content: module Strategy.Gomodules (
    discover,
    findProjects,
    getDeps,
    mkProject,
    GomodulesProject (..),
  )
  where
  
  import App.Fossa.Analyze.Types (AnalyzeProject (analyzeProjectStaticOnly), analyzeProject)
  import App.Fossa.Config.Analyze (ExperimentalAnalyzeConfig (useV3GoResolver), GoDynamicTactic (..))
  import App.Util (guardStrictMode)
  import Control.Carrier.Diagnostics (warn)
  import Control.Effect.Diagnostics (Diagnostics, context, recover, (<||>))
  import Control.Effect.Reader (Reader, ask, asks)
  import Control.Monad (when)
  import Data.Aeson (ToJSON)
  import Data.Text (Text)
  import Discovery.Filters (AllFilters)
  import Discovery.Simple (simpleDiscover)
  import Discovery.Walk (
    WalkStep (WalkSkipSome),
    findFileNamed,
    walkWithFilters',
   )
  import Effect.Exec (Exec, GetDepsEffs, Has)
  import Effect.ReadFS (ReadFS)
  import GHC.Generics (Generic)
  import Graphing (Graphing)
  import Path (Abs, Dir, File, Path)
  import Strategy.Go.GoListPackages qualified as GoListPackages
  import Strategy.Go.Gomod qualified as Gomod
  import Strategy.Go.Gostd (GoStdlibDep, filterGoStdlibPackages, listGoStdlibPackages)
  import Types (
    Dependency,
    DependencyResults (..),
    DiscoveredProject (..),
    DiscoveredProjectType (GomodProjectType),
    GraphBreadth,
   )
  
  discover :: (Has ReadFS sig m, Has Diagnostics sig m, Has (Reader AllFilters) sig m) => Path Abs Dir -> m [DiscoveredProject GomodulesProject]
  discover = simpleDiscover findProjects mkProject GomodProjectType
  
  findProjects :: (Has ReadFS sig m, Has Diagnostics sig m, Has (Reader AllFilters) sig m) => Path Abs Dir -> m [GomodulesProject]
  findProjects = walkWithFilters' $ \dir _ files -> do
    case findFileNamed "go.mod" files of
      Nothing -> pure ([], WalkSkipSome ["vendor"])
      Just gomod -> pure ([GomodulesProject gomod dir], WalkSkipSome ["vendor"])
  
  data GomodulesProject = GomodulesProject
    { gomodulesGomod :: Path Abs File
    , gomodulesDir :: Path Abs Dir
    }
    deriving (Eq, Ord, Show, Generic)
  
  instance ToJSON GomodulesProject
  
  instance AnalyzeProject GomodulesProject where
    analyzeProject _ proj = asks useV3GoResolver >>= getDeps proj
    analyzeProjectStaticOnly _ = staticAnalysis
  
  mkProject :: GomodulesProject -> DiscoveredProject GomodulesProject
  mkProject project =
    DiscoveredProject
      { projectType = GomodProjectType
      , projectBuildTargets = mempty
      , projectPath = gomodulesDir project
      , projectData = project
      }
  
  getDeps :: (GetDepsEffs sig m) => GomodulesProject -> GoDynamicTactic -> m DependencyResults
  getDeps project goDynamicTactic = do
    mode <- ask
    (graph, graphBreadth) <-
      context "Gomodules" $
        dynamicAnalysis <||> guardStrictMode mode goDotModAnalysis
    stdlib <- recover . context "Collect go standard library information" . listGoStdlibPackages $ gomodulesDir project
    pure $
      DependencyResults
        { dependencyGraph = filterGraph stdlib graph
        , dependencyGraphBreadth = graphBreadth
        , dependencyManifestFiles = [gomodulesGomod project]
        }
    where
      -- `go list -json -deps all` marks std lib deps with a boolean, so Strategy.Go.GoListPackages does this filtering itself.
      -- I think this can be removed when `go list -json -deps all` becomes the default.
      filterGraph :: Maybe [GoStdlibDep] -> Graphing Dependency -> Graphing Dependency
      filterGraph Nothing deps = deps
      filterGraph (Just stdlib) deps = filterGoStdlibPackages stdlib deps
  
      goDotModAnalysis :: (Has ReadFS sig m, Has Exec sig m, Has Diagnostics sig m) => m (Graphing Dependency, GraphBreadth)
      goDotModAnalysis = context "Go.mod analysis" (Gomod.analyze' (gomodulesGomod project))
  
      dynamicAnalysis :: (Has Exec sig m, Has Diagnostics sig m) => m (Graphing Dependency, GraphBreadth)
      dynamicAnalysis =
        context "Dynamic analysis" $ do
          when (goDynamicTactic == GoPackagesBasedTactic) $
            warn @Text
              "--experimental-use-v3-go-resolver is now deprecated because the v3 resolver is the default. \
              \This option will be removed in a future release and result in an error."
  
          context "analysis using go list (V3 Resolver)" (GoListPackages.analyze (gomodulesDir project))
  
  staticAnalysis :: (Has Diagnostics sig m, Has ReadFS sig m) => GomodulesProject -> m DependencyResults
  staticAnalysis proj = do
    let projectPath = gomodulesGomod proj
    (graph, breadth) <- context "Go.mod analysis (static)" $ Gomod.analyzeStatic projectPath
    pure $
      DependencyResults
        { dependencyGraph = graph
        , dependencyGraphBreadth = breadth
        , dependencyManifestFiles = [projectPath]
        }
  