package linter import ( "os" "path/filepath" "testing" ) func TestDetectLanguage(t *testing.T) { tests := []struct { name string filePath string wantLang string wantErr bool }{ {"Go file", "main.go", "Go", false}, {"Python file", "script.py", "Python", false}, {"JavaScript file", "app.js", "JavaScript", false}, {"JSX file", "component.jsx", "JavaScript", false}, {"TypeScript file", "app.ts", "TypeScript", false}, {"TSX file", "component.tsx", "TypeScript", false}, {"Rust file", "main.rs", "Rust", false}, {"Ruby file", "script.rb", "Ruby", false}, {"Java file", "Main.java", "Java", false}, {"C file", "program.c", "C/C++", false}, {"C++ file", "program.cpp", "C/C++", false}, {"Header file", "header.h", "C/C++", false}, {"Shell script", "script.sh", "Shell", false}, {"Bash script", "script.bash", "Shell", false}, {"Unsupported file", "file.txt", "", true}, {"No extension", "Makefile", "", true}, {"Case insensitive", "MAIN.GO", "Go", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lang, err := DetectLanguage(tt.filePath) if tt.wantErr { if err == nil { t.Errorf("DetectLanguage(%q) expected error, got nil", tt.filePath) } return } if err != nil { t.Errorf("DetectLanguage(%q) unexpected error: %v", tt.filePath, err) return } if lang.Name != tt.wantLang { t.Errorf("DetectLanguage(%q) = %q, want %q", tt.filePath, lang.Name, tt.wantLang) } }) } } func TestCheckLinterAvailable(t *testing.T) { tests := []struct { name string linter Linter wantMsg string }{ { name: "go command should be available", linter: Linter{ Name: "go", Command: "go", }, wantMsg: "go should be available on system with Go installed", }, { name: "nonexistent command", linter: Linter{ Name: "nonexistent", Command: "this-command-does-not-exist-12345", }, wantMsg: "nonexistent command should not be available", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { available := CheckLinterAvailable(tt.linter) // We can't assert exact true/false since it depends on the system // Just verify the function runs without panic t.Logf("%s: available=%v", tt.wantMsg, available) }) } } func TestFindAvailableLinter(t *testing.T) { // Test with Go language since 'go' command should be available // (we're running tests with Go installed) t.Run("Go language should find a linter", func(t *testing.T) { goLang := &Language{ Name: "Go", Extensions: []string{".go"}, Linters: []Linter{ { Name: "go vet", Command: "go", Args: []string{"vet"}, InstallInfo: "Built-in with Go", }, }, } linter, err := FindAvailableLinter(goLang) if err != nil { t.Errorf("FindAvailableLinter(Go) expected to find 'go', got error: %v", err) } if linter == nil { t.Errorf("FindAvailableLinter(Go) returned nil linter") } else if linter.Command != "go" { t.Errorf("FindAvailableLinter(Go) = %q, want 'go'", linter.Command) } }) t.Run("Language with no available linters", func(t *testing.T) { fakeLang := &Language{ Name: "FakeLang", Extensions: []string{".fake"}, Linters: []Linter{ { Name: "fakelint", Command: "this-does-not-exist-12345", InstallInfo: "not installable", }, }, } linter, err := FindAvailableLinter(fakeLang) if err == nil { t.Errorf("FindAvailableLinter(FakeLang) expected error, got linter: %v", linter) } if linter != nil { t.Errorf("FindAvailableLinter(FakeLang) expected nil linter, got: %v", linter) } }) } func TestRunLinter(t *testing.T) { // Create a temporary Go file with a simple error tmpDir := t.TempDir() testFile := filepath.Join(tmpDir, "test.go") // Valid Go code validCode := `package main import "fmt" func main() { fmt.Println("Hello, World!") } ` if err := os.WriteFile(testFile, []byte(validCode), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } t.Run("Run go vet on valid file", func(t *testing.T) { linter := Linter{ Name: "go vet", Command: "go", Args: []string{"vet"}, } result, err := RunLinter(testFile, linter) if err != nil { t.Errorf("RunLinter() unexpected error: %v", err) } if result == nil { t.Fatal("RunLinter() returned nil result") } if !result.LinterExists { t.Errorf("RunLinter() LinterExists = false, want true") } if result.LinterUsed != "go vet" { t.Errorf("RunLinter() LinterUsed = %q, want 'go vet'", result.LinterUsed) } // Note: go vet might find issues or not, so we don't assert HasIssues t.Logf("go vet result: ExitCode=%d, HasIssues=%v, Output=%q", result.ExitCode, result.HasIssues, result.Output) }) t.Run("Run nonexistent linter", func(t *testing.T) { linter := Linter{ Name: "fake", Command: "this-does-not-exist-12345", Args: []string{}, } result, err := RunLinter(testFile, linter) if err == nil { t.Errorf("RunLinter() expected error for nonexistent command, got result: %v", result) } }) } func TestLintFile(t *testing.T) { tmpDir := t.TempDir() t.Run("Lint valid Go file", func(t *testing.T) { testFile := filepath.Join(tmpDir, "valid.go") validCode := `package main import "fmt" func main() { fmt.Println("Hello, World!") } ` if err := os.WriteFile(testFile, []byte(validCode), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } result, err := LintFile(testFile) if err != nil { t.Errorf("LintFile() unexpected error: %v", err) } if result == nil { t.Fatal("LintFile() returned nil result") } if result.Language != "Go" { t.Errorf("LintFile() Language = %q, want 'Go'", result.Language) } if !result.LinterExists { t.Errorf("LintFile() LinterExists = false, want true") } }) t.Run("Lint nonexistent file", func(t *testing.T) { testFile := filepath.Join(tmpDir, "nonexistent.go") result, err := LintFile(testFile) if err == nil { t.Errorf("LintFile() expected error for nonexistent file, got result: %v", result) } }) t.Run("Lint unsupported file type", func(t *testing.T) { testFile := filepath.Join(tmpDir, "test.txt") if err := os.WriteFile(testFile, []byte("hello"), 0644); err != nil { t.Fatalf("Failed to create test file: %v", err) } result, err := LintFile(testFile) if err == nil { t.Errorf("LintFile() expected error for unsupported file type, got result: %v", result) } }) } func TestGetSupportedLanguages(t *testing.T) { langs := GetSupportedLanguages() if len(langs) == 0 { t.Errorf("GetSupportedLanguages() returned empty list") } // Check that we have some expected languages expectedLangs := []string{"Go", "Python", "JavaScript", "TypeScript"} for _, expected := range expectedLangs { found := false for _, lang := range langs { if contains(lang, expected) { found = true break } } if !found { t.Errorf("GetSupportedLanguages() missing expected language: %s", expected) } } // Verify format includes extensions for _, lang := range langs { if !contains(lang, "(") || !contains(lang, ")") { t.Errorf("GetSupportedLanguages() entry missing format 'Name (extensions)': %s", lang) } } } func TestLanguageStructure(t *testing.T) { // Verify the languages slice is properly structured if len(languages) == 0 { t.Fatal("languages slice is empty") } for _, lang := range languages { if lang.Name == "" { t.Errorf("Language with empty Name found") } if len(lang.Extensions) == 0 { t.Errorf("Language %q has no extensions", lang.Name) } if len(lang.Linters) == 0 { t.Errorf("Language %q has no linters", lang.Name) } // Check extensions start with dot for _, ext := range lang.Extensions { if ext == "" || ext[0] != '.' { t.Errorf("Language %q has invalid extension: %q", lang.Name, ext) } } // Check linters have required fields for _, linter := range lang.Linters { if linter.Name == "" { t.Errorf("Language %q has linter with empty Name", lang.Name) } if linter.Command == "" { t.Errorf("Language %q has linter %q with empty Command", lang.Name, linter.Name) } if linter.InstallInfo == "" { t.Errorf("Language %q has linter %q with empty InstallInfo", lang.Name, linter.Name) } } } } // Helper function func contains(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr))) } func containsMiddle(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }