@@ -19,6 +19,8 @@ namespace Microsoft.ComponentDetection.Detectors.Go;
1919
2020public class GoComponentDetector : FileComponentDetector
2121{
22+ private const string StartString = "require " ;
23+
2224 private static readonly Regex GoSumRegex = new (
2325 @"(?<name>.*)\s+(?<version>.*?)(/go\.mod)?\s+(?<hash>.*)" ,
2426 RegexOptions . Compiled | RegexOptions . ExplicitCapture | RegexOptions . IgnoreCase ) ;
@@ -27,19 +29,22 @@ public class GoComponentDetector : FileComponentDetector
2729
2830 private readonly ICommandLineInvocationService commandLineInvocationService ;
2931 private readonly IEnvironmentVariableService envVarService ;
32+ private readonly IFileUtilityService fileUtilityService ;
3033
3134 public GoComponentDetector (
3235 IComponentStreamEnumerableFactory componentStreamEnumerableFactory ,
3336 IObservableDirectoryWalkerFactory walkerFactory ,
3437 ICommandLineInvocationService commandLineInvocationService ,
3538 IEnvironmentVariableService envVarService ,
36- ILogger < GoComponentDetector > logger )
39+ ILogger < GoComponentDetector > logger ,
40+ IFileUtilityService fileUtilityService )
3741 {
3842 this . ComponentStreamEnumerableFactory = componentStreamEnumerableFactory ;
3943 this . Scanner = walkerFactory ;
4044 this . commandLineInvocationService = commandLineInvocationService ;
4145 this . envVarService = envVarService ;
4246 this . Logger = logger ;
47+ this . fileUtilityService = fileUtilityService ;
4348 }
4449
4550 public override string Id => "Go" ;
@@ -50,7 +55,7 @@ public GoComponentDetector(
5055
5156 public override IEnumerable < ComponentType > SupportedComponentTypes { get ; } = [ ComponentType . Go ] ;
5257
53- public override int Version => 7 ;
58+ public override int Version => 8 ;
5459
5560 protected override Task < IObservable < ProcessRequest > > OnPrepareDetectionAsync (
5661 IObservable < ProcessRequest > processRequests ,
@@ -180,6 +185,7 @@ protected override async Task OnFileFoundAsync(ProcessRequest processRequest, ID
180185 catch ( Exception ex )
181186 {
182187 this . Logger . LogError ( ex , "Failed to detect components using go cli. Location: {Location}" , file . Location ) ;
188+ record . ExceptionMessage = ex . Message ;
183189 }
184190 finally
185191 {
@@ -246,7 +252,7 @@ private async Task<bool> UseGoCliToScanAsync(string location, ISingleFileCompone
246252 return false ;
247253 }
248254
249- this . RecordBuildDependencies ( goDependenciesProcess . StdOut , singleFileComponentRecorder ) ;
255+ this . RecordBuildDependencies ( goDependenciesProcess . StdOut , singleFileComponentRecorder , projectRootDirectory . FullName ) ;
250256
251257 var generateGraphProcess = await this . commandLineInvocationService . ExecuteCommandAsync ( "go" , null , workingDirectory : projectRootDirectory , new List < string > { "mod" , "graph" } . ToArray ( ) ) ;
252258 if ( generateGraphProcess . ExitCode == 0 )
@@ -299,9 +305,9 @@ private async Task ParseGoModFileAsync(
299305
300306 // In go >= 1.17, direct dependencies are listed as "require x/y v1.2.3", and transitive dependencies
301307 // are listed in the require () section
302- if ( line . StartsWith ( "require " ) )
308+ if ( line . StartsWith ( StartString ) )
303309 {
304- this . TryRegisterDependencyFromModLine ( line [ 8 ..] , singleFileComponentRecorder ) ;
310+ this . TryRegisterDependencyFromModLine ( line [ StartString . Length ..] , singleFileComponentRecorder ) ;
305311 }
306312
307313 line = await reader . ReadLineAsync ( ) ;
@@ -421,14 +427,16 @@ private bool IsModuleInBuildList(ISingleFileComponentRecorder singleFileComponen
421427 return singleFileComponentRecorder . GetComponent ( component . Id ) != null ;
422428 }
423429
424- private void RecordBuildDependencies ( string goListOutput , ISingleFileComponentRecorder singleFileComponentRecorder )
430+ private void RecordBuildDependencies ( string goListOutput , ISingleFileComponentRecorder singleFileComponentRecorder , string projectRootDirectoryFullName )
425431 {
426432 var goBuildModules = new List < GoBuildModule > ( ) ;
427433 var reader = new JsonTextReader ( new StringReader ( goListOutput ) )
428434 {
429435 SupportMultipleContent = true ,
430436 } ;
431437
438+ using var record = new GoReplaceTelemetryRecord ( ) ;
439+
432440 while ( reader . Read ( ) )
433441 {
434442 var serializer = new JsonSerializer ( ) ;
@@ -439,13 +447,45 @@ private void RecordBuildDependencies(string goListOutput, ISingleFileComponentRe
439447
440448 foreach ( var dependency in goBuildModules )
441449 {
450+ var dependencyName = $ "{ dependency . Path } { dependency . Version } ";
451+
442452 if ( dependency . Main )
443453 {
444454 // main is the entry point module (superfluous as we already have the file location)
445455 continue ;
446456 }
447457
448- var goComponent = new GoComponent ( dependency . Path , dependency . Version ) ;
458+ if ( dependency . Replace ? . Path != null && dependency . Replace . Version == null )
459+ {
460+ var dirName = projectRootDirectoryFullName ;
461+ var combinedPath = Path . Combine ( dirName , dependency . Replace . Path , "go.mod" ) ;
462+ var goModFilePath = Path . GetFullPath ( combinedPath ) ;
463+ if ( this . fileUtilityService . Exists ( goModFilePath ) )
464+ {
465+ this . Logger . LogInformation ( "go Module {GoModule} is being replaced with module at path {GoModFilePath}" , dependencyName , goModFilePath ) ;
466+ record . GoModPathAndVersion = dependencyName ;
467+ record . GoModReplacement = goModFilePath ;
468+ continue ;
469+ }
470+
471+ this . Logger . LogWarning ( "go.mod file {GoModFilePath} does not exist in the relative path given for replacement" , goModFilePath ) ;
472+ record . GoModPathAndVersion = goModFilePath ;
473+ record . GoModReplacement = null ;
474+ }
475+
476+ GoComponent goComponent ;
477+ if ( dependency . Replace ? . Path != null && dependency . Replace . Version != null )
478+ {
479+ var dependencyReplacementName = $ "{ dependency . Replace . Path } { dependency . Replace . Version } ";
480+ goComponent = new GoComponent ( dependency . Replace . Path , dependency . Replace . Version ) ;
481+ this . Logger . LogInformation ( "go Module {GoModule} being replaced with module {GoModuleReplacement}" , dependencyName , dependencyReplacementName ) ;
482+ record . GoModPathAndVersion = dependencyName ;
483+ record . GoModReplacement = dependencyReplacementName ;
484+ }
485+ else
486+ {
487+ goComponent = new GoComponent ( dependency . Path , dependency . Version ) ;
488+ }
449489
450490 if ( dependency . Indirect )
451491 {
@@ -485,5 +525,7 @@ private class GoBuildModule
485525 public string Version { get ; set ; }
486526
487527 public bool Indirect { get ; set ; }
528+
529+ public GoBuildModule Replace { get ; set ; }
488530 }
489531}
0 commit comments