@@ -16,7 +16,9 @@ import (
1616 "codacy/cli-v2/plugins"
1717 "codacy/cli-v2/tools"
1818 "codacy/cli-v2/utils"
19+ "codacy/cli-v2/utils/logger"
1920
21+ "github.com/sirupsen/logrus"
2022 "github.com/spf13/cobra"
2123 "gopkg.in/yaml.v3"
2224)
@@ -165,8 +167,24 @@ var configDiscoverCmd = &cobra.Command{
165167
166168 fmt .Printf ("Discovering languages and tools for path: %s\n " , discoverPath )
167169
170+ // Detect file extensions first
171+ extCount , err := detectFileExtensions (discoverPath )
172+ if err != nil {
173+ log .Fatalf ("Error detecting file extensions: %v" , err )
174+ }
175+
168176 defaultToolLangMap := tools .GetDefaultToolLanguageMapping ()
169177
178+ if len (extCount ) > 0 {
179+ recognizedExts := getRecognizableExtensions (extCount , defaultToolLangMap )
180+ if len (recognizedExts ) > 0 {
181+ logger .Debug ("Detected recognizable file extensions" , logrus.Fields {
182+ "extensions" : recognizedExts ,
183+ "path" : discoverPath ,
184+ })
185+ }
186+ }
187+
170188 detectedLanguages , err := detectLanguagesInPath (discoverPath , defaultToolLangMap )
171189 if err != nil {
172190 log .Fatalf ("Error detecting languages: %v" , err )
@@ -175,7 +193,10 @@ var configDiscoverCmd = &cobra.Command{
175193 fmt .Println ("No known languages detected in the provided path." )
176194 return
177195 }
178- fmt .Printf ("Detected languages: %v\n " , getSortedKeys (detectedLanguages ))
196+ logger .Debug ("Detected languages in path" , logrus.Fields {
197+ "languages" : getSortedKeys (detectedLanguages ),
198+ "path" : discoverPath ,
199+ })
179200
180201 toolsConfigDir := config .Config .ToolsConfigsDirectory ()
181202 if err := updateLanguagesConfig (detectedLanguages , toolsConfigDir , defaultToolLangMap ); err != nil {
@@ -192,13 +213,16 @@ var configDiscoverCmd = &cobra.Command{
192213
193214 codacyYAMLPath := config .Config .ProjectConfigFile ()
194215 if err := updateCodacyYAML (detectedLanguages , codacyYAMLPath , defaultToolLangMap , configResetInitFlags , currentCliMode ); err != nil {
216+ if strings .Contains (err .Error (), "❌ Fatal:" ) {
217+ fmt .Println (err )
218+ os .Exit (1 )
219+ }
195220 log .Fatalf ("Error updating %s: %v" , codacyYAMLPath , err )
196221 }
197222 fmt .Printf ("Updated %s with relevant tools.\n " , filepath .Base (codacyYAMLPath ))
198223
199224 fmt .Println ("\n ✅ Successfully discovered languages and updated configurations." )
200225 fmt .Println (" Please review the changes in '.codacy/codacy.yaml' and '.codacy/tools-configs/languages-config.yaml'." )
201- fmt .Println (" Run 'codacy-cli install' to install any newly added tools." )
202226 },
203227}
204228
@@ -211,11 +235,12 @@ func getSortedKeys(m map[string]struct{}) []string {
211235 return keys
212236}
213237
214- // detectLanguagesInPath walks the directory and detects languages based on file extensions.
238+ // detectLanguagesInPath detects languages based on file extensions found in the path .
215239func detectLanguagesInPath (rootPath string , toolLangMap map [string ]domain.ToolLanguageInfo ) (map [string ]struct {}, error ) {
216240 detectedLangs := make (map [string ]struct {})
217241 extToLang := make (map [string ][]string )
218242
243+ // Build extension to language mapping
219244 for _ , toolInfo := range toolLangMap {
220245 for _ , lang := range toolInfo .Languages {
221246 if lang == "Multiple" || lang == "Generic" { // Skip generic language types for direct detection
@@ -227,23 +252,19 @@ func detectLanguagesInPath(rootPath string, toolLangMap map[string]domain.ToolLa
227252 }
228253 }
229254
230- err := filepath .Walk (rootPath , func (path string , info os.FileInfo , err error ) error {
231- if err != nil {
232- return err
233- }
234- if ! info .IsDir () {
235- ext := strings .ToLower (filepath .Ext (path ))
236- if langs , ok := extToLang [ext ]; ok {
237- for _ , lang := range langs {
238- detectedLangs [lang ] = struct {}{}
239- }
255+ // Get file extensions from the path
256+ extCount , err := detectFileExtensions (rootPath )
257+ if err != nil {
258+ return nil , fmt .Errorf ("failed to detect file extensions in path %s: %w" , rootPath , err )
259+ }
260+
261+ // Map extensions to languages
262+ for ext := range extCount {
263+ if langs , ok := extToLang [ext ]; ok {
264+ for _ , lang := range langs {
265+ detectedLangs [lang ] = struct {}{}
240266 }
241267 }
242- return nil
243- })
244-
245- if err != nil {
246- return nil , fmt .Errorf ("failed to walk path %s: %w" , rootPath , err )
247268 }
248269
249270 return detectedLangs , nil
@@ -355,15 +376,19 @@ func updateCodacyYAML(detectedLanguages map[string]struct{}, codacyYAMLPath stri
355376 return fmt .Errorf ("error reading %s: %w" , codacyYAMLPath , err )
356377 }
357378 if err := yaml .Unmarshal (content , & configData ); err != nil {
358- return fmt .Errorf ("error parsing %s: %w" , codacyYAMLPath , err )
379+ if strings .Contains (err .Error (), "cannot unmarshal" ) {
380+ return fmt .Errorf (
381+ "❌ Fatal: %s contains invalid configuration - run 'codacy-cli config reset' to fix: %v" ,
382+ filepath .Base (codacyYAMLPath ), err )
383+ }
384+ return fmt .Errorf (
385+ "❌ Fatal: %s is broken or has invalid YAML format - run 'codacy-cli config reset' to reinitialize your configuration" ,
386+ filepath .Base (codacyYAMLPath ))
359387 }
360388 } else if os .IsNotExist (err ) {
361- // If codacy.yaml doesn't exist, create a basic structure
362- configData = make (map [string ]interface {})
363- configData ["tools" ] = []interface {}{}
364- configData ["runtimes" ] = []interface {}{} // Initialize runtimes as well
389+ return fmt .Errorf ("codacy.yaml file not found" )
365390 } else {
366- return fmt .Errorf ("error statting %s: %w" , codacyYAMLPath , err )
391+ return fmt .Errorf ("error accessing %s: %w" , codacyYAMLPath , err )
367392 }
368393
369394 toolsRaw , _ := configData ["tools" ].([]interface {})
@@ -533,6 +558,70 @@ func updateCodacyYAML(detectedLanguages map[string]struct{}, codacyYAMLPath stri
533558 return os .WriteFile (codacyYAMLPath , yamlData , utils .DefaultFilePerms )
534559}
535560
561+ // detectFileExtensions walks the directory and collects all unique file extensions with their counts
562+ func detectFileExtensions (rootPath string ) (map [string ]int , error ) {
563+ extCount := make (map [string ]int )
564+
565+ err := filepath .Walk (rootPath , func (path string , info os.FileInfo , err error ) error {
566+ if err != nil {
567+ return err
568+ }
569+ if ! info .IsDir () {
570+ ext := strings .ToLower (filepath .Ext (path ))
571+ if ext != "" {
572+ extCount [ext ]++
573+ }
574+ }
575+ return nil
576+ })
577+
578+ if err != nil {
579+ return nil , fmt .Errorf ("failed to walk path %s: %w" , rootPath , err )
580+ }
581+
582+ return extCount , nil
583+ }
584+
585+ // getRecognizableExtensions returns a sorted list of extensions that are mapped to languages
586+ func getRecognizableExtensions (extCount map [string ]int , toolLangMap map [string ]domain.ToolLanguageInfo ) []string {
587+ // Build set of recognized extensions
588+ recognizedExts := make (map [string ]struct {})
589+ for _ , toolInfo := range toolLangMap {
590+ for _ , ext := range toolInfo .Extensions {
591+ recognizedExts [ext ] = struct {}{}
592+ }
593+ }
594+
595+ // Filter and format recognized extensions with counts
596+ type extInfo struct {
597+ ext string
598+ count int
599+ }
600+ var recognizedExtList []extInfo
601+
602+ for ext , count := range extCount {
603+ if _ , ok := recognizedExts [ext ]; ok {
604+ recognizedExtList = append (recognizedExtList , extInfo {ext , count })
605+ }
606+ }
607+
608+ // Sort by count (descending) and then by extension name
609+ sort .Slice (recognizedExtList , func (i , j int ) bool {
610+ if recognizedExtList [i ].count != recognizedExtList [j ].count {
611+ return recognizedExtList [i ].count > recognizedExtList [j ].count
612+ }
613+ return recognizedExtList [i ].ext < recognizedExtList [j ].ext
614+ })
615+
616+ // Format extensions with their counts
617+ result := make ([]string , len (recognizedExtList ))
618+ for i , info := range recognizedExtList {
619+ result [i ] = fmt .Sprintf ("%s (%d files)" , info .ext , info .count )
620+ }
621+
622+ return result
623+ }
624+
536625func init () {
537626 // Define flags for the config reset command. These are the same flags used by the init command.
538627 configResetCmd .Flags ().StringVar (& configResetInitFlags .ApiToken , "api-token" , "" , "Optional Codacy API token. If defined, configurations will be fetched from Codacy." )
0 commit comments