src/module/Update-RootModuleUsingStatements.ps1

using module .\.\MKModuleInfo.psm1
using module .\manifest\AutoUpdateSemVerDelegate.ps1

function Update-RootModuleUsingStatements {
    [CmdletBinding(PositionalBinding = $true, 
        DefaultParameterSetName = "ByPath")]
    Param
    (
        [Parameter(Mandatory = $false,
            Position = 0,
            ValueFromPipeline = $false, 
            ParameterSetName = "ByPath")]
        [string]$Path = '.',

        [Parameter(Mandatory = $false,
            Position = 1,
            ValueFromPipeline = $true, 
            ParameterSetName = "ByPipe")]
        [MKModuleInfo]$ModInfo,

        [Parameter(Mandatory = $false)]
        [string]$SourceFolderPath = 'src',

        [Parameter(Mandatory = $false)]
        [string[]]$Include = @('*.ps1', '*.psm1'),

        [Parameter(Mandatory = $false)]
        [string[]]$Exclude = '*.Tests.ps1',

        [switch]
        $PassThru
    )
    
    DynamicParam {
        return GetModuleNameSet -Position 0 -Mandatory 
    }

    begin {
        $Name = $PSBoundParameters['Name']

        # Prevents single space for each item in an iteration:
        # https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_preference_variables?view=powershell-6#ofs
        $OFS = ''
    }

    end {

        if ((-not $ModInfo) -and (-not $Name)) {
            $ModInfo = Get-MKModuleInfo -Path $Path
        }
        elseif (-not $ModInfo) {
            $ModInfo = Get-MKModuleInfo -Name $Name
        }
        
        AutoUpdateSemVerDelegate($ModInfo.Path)

        $TargetDirectory = Join-Path -Path $ModInfo.Path -ChildPath $SourceFolderPath -Resolve

        # $StopMatchingImportStatements: stop matching when there is a break of consecutive
        # 'using module' statements. a break with additional statements means that developer
        # manually added that line; so keep it.
        $RootModuleContent = Get-Content $ModInfo.RootModuleFilePath
        [string[]]$ModuleContentsCleaned = $RootModuleContent | `
            ForEach-Object -Begin {$StopMatchingImportStatements = $false} -Process {
            if ($StopMatchingImportStatements) {
                $($_ + "`n")
            }
            elseif (-not ($_ -match '(?<=(using module \.\\)).*(?=(\.ps1))')) {
                if ($_ -match '\S+') {
                    $($_ + "`n")
                }
                $StopMatchingImportStatements = $true
            }
        }

        $TargetFunctionsToExport = Get-ChildItem -Path $TargetDirectory -Include $Include -Exclude $Exclude -Recurse | `
            Get-Item -Include $Include -PipelineVariable File | `
            Get-Content -Raw | `
            ForEach-Object {
            $NoExportMatches = [regex]::Matches($_, '(?<=NoExport)(?:[:\s]*?)(?<sanitized>\w*-\w*)')
            $FunctionMatches = [regex]::Matches($_, '(?<=function )[\w]*[-][\w]*')
            for ($i = 0; $i -lt $FunctionMatches.Count; $i++) {
                $FunctionName = $FunctionMatches[$i].Value
                if (($NoExportMatches | ForEach-Object {$_.Groups['sanitized'].Value}) -notcontains $FunctionName) {
                    @{
                        FilePath     = $File
                        FunctionName = $FunctionName
                    }
                }
            }
        }

        [string[]]$UniqueSourceFiles = $TargetFunctionsToExport.FilePath.FullName | `
            Sort-Object -Unique | `
            Group-Object -Property {$_.Split("$SourceFolderPath\")[1]} | `
            Select-Object -ExpandProperty Group | `
            ForEach-Object {
            if ($_ -ne $ModInfo.RootModuleFilePath) {
                $("using module .$($_.Split($ModInfo.Path)[1])".Trim() + "`n")
            }
        }
    
        if ($ModuleContentsCleaned -eq $null) {
            $UpdatedModuleContentRaw = @"
$UniqueSourceFiles
"@

        }
        elseif ($ModuleContentsCleaned -ne $null) {
            $UpdatedModuleContentRaw = @"
$UniqueSourceFiles
$ModuleContentsCleaned
"@

        }
    
        @{
            ManifestPath            = $ModInfo.ManifestFilePath
            TargetFunctionsToExport = $TargetFunctionsToExport
        }

        if ($UpdatedModuleContentRaw) {
            $UpdatedModuleContent = $($UpdatedModuleContentRaw -split "`n").TrimEnd("`r")
        }
        else {
            $UpdatedModuleContent = @()
        }

        if (-not $RootModuleContent) {
            $RootModuleContent = @()
        }

        $Differences = Compare-Object -ReferenceObject $UpdatedModuleContent -DifferenceObject $RootModuleContent -PassThru
        
        # touch the file only when there is a difference
        if ($Differences) {
            Set-Content -Path $ModInfo.RootModuleFilePath -Value $UpdatedModuleContentRaw -PassThru:$PassThru.IsPresent -Encoding UTF8 -NoNewline
        }

        $OFS = ' '
    }
}