Private/FileSystem.ps1

function Get-ScanTarget {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string]$Path,

        [switch]$Recurse,

        [string[]]$Include,

        [string[]]$Exclude
    )

    # Literal first, so names like 'scripts[old]' work; then wildcard paths such as '.\*.ps1'.
    if (Test-Path -LiteralPath $Path) {
        $resolvedItems = @(Resolve-Path -LiteralPath $Path)
    }
    else {
        $resolvedItems = @(Resolve-Path -Path $Path -ErrorAction SilentlyContinue)
    }
    if ($resolvedItems.Count -eq 0) {
        Write-Error -Message "Path not found: $Path" -Category ObjectNotFound -TargetObject $Path
        return
    }

    foreach ($resolved in $resolvedItems) {
        $item = Get-Item -LiteralPath $resolved.ProviderPath -Force
        if ($item.PSIsContainer) {
            # Symbolic links found while walking a folder are skipped: they may point outside the
            # folder (for example a link to a secrets file committed in a pull request).
            $files = Get-ChildItem -LiteralPath $item.FullName -File -Recurse:$Recurse -Force -ErrorAction SilentlyContinue |
                Where-Object {
                    $name = $_.Name
                    -not (Test-ReparsePoint -Item $_) -and @($Include | Where-Object { $name -like $_ }).Count -gt 0
                }
        }
        else {
            # A file passed explicitly is always scanned, whatever its extension.
            $files = @($item)
        }

        foreach ($file in $files) {
            $fullName = $file.FullName
            # Compare with forward slashes so '*/tests/*' works on Windows and Linux alike.
            $normalized = $fullName.Replace('\', '/')
            if ($Exclude -and @($Exclude | Where-Object { $normalized -like $_.Replace('\', '/') }).Count -gt 0) {
                continue
            }
            $fullName
        }
    }
}

function Write-ParseErrorWarning {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [System.Management.Automation.Language.ParseError[]]$ParseErrors,

        [Parameter(Mandatory)]
        [string]$Source
    )

    # 'using module X' reports a parse error when X is not installed. That is expected when scanning
    # legacy scripts (the whole point is that MSOnline & co. are gone), so it is not worth a warning.
    $relevant = @($ParseErrors | Where-Object { $_ -and $_.ErrorId -ne 'ModuleNotFoundDuringParse' })
    if ($relevant.Count -gt 0) {
        $first = $relevant[0]
        $text = '{0} has {1} syntax error(s), results may be incomplete. First: line {2}: {3}' -f $Source, $relevant.Count, $first.Extent.StartLineNumber, $first.Message
        # Parse error messages quote the scanned code. Keep them on one line so scanned content can
        # never start a new log line (CI systems interpret lines such as '::warning::' as commands).
        Write-Warning (ConvertTo-SingleLine -Text $text)
    }
}


function Test-ReparsePoint {
    <#
    .SYNOPSIS
        True for symbolic links, junctions and other reparse points.
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)]
        [System.IO.FileSystemInfo]$Item
    )
    return [bool]($Item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)
}

function ConvertTo-SingleLine {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [AllowNull()]
        [AllowEmptyString()]
        [string]$Text
    )
    if ($null -eq $Text) { return '' }
    return ($Text -replace '[\r\n\u0085\u2028\u2029]+', ' ')
}

function Assert-WritableReportTarget {
    <#
    .SYNOPSIS
        Refuses to write through a symbolic link or over an existing file without -Force.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$Path,

        [switch]$Force
    )
    if (-not (Test-Path -LiteralPath $Path)) {
        return
    }
    $existing = Get-Item -LiteralPath $Path -Force
    if (Test-ReparsePoint -Item $existing) {
        throw "Refusing to write '$Path' because it is a symbolic link or junction."
    }
    if ($existing.PSIsContainer) {
        throw "'$Path' is a folder. Pass a file path."
    }
    if (-not $Force) {
        throw "File '$Path' already exists. Use -Force to overwrite it."
    }
}