Common/Includes.ps1

#----------------------------------------------------------------------------------------------------------------------
# MIT License
#
# Copyright (c) 2025 Mark Schofield
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#----------------------------------------------------------------------------------------------------------------------
#Requires -PSEdition Core

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

<#
    .Synopsis
    Invokes the compiler and returns combined stdout+stderr as an array of strings.
 
    .Description
    A function wrapping the compiler invocation, allowing it to be mocked for testing.
#>

function InvokeCompilerForIncludes {
    param(
        [string] $Path,
        [string[]] $Arguments
    )
    & $Path @Arguments 2>&1 | ForEach-Object { "$_" }
}

<#
    .Synopsis
    Reads the toolchains-v1 File API reply for the given binary directory.
#>

function Get-CMakeBuildToolchains {
    param(
        [string] $BinaryDirectory
    )
    Get-ChildItem -LiteralPath (Get-CMakeBuildCodeModelDirectory $BinaryDirectory) -File -Filter 'toolchains-v1-*' -ErrorAction SilentlyContinue |
        Select-Object -First 1 |
        Get-Content |
        ConvertFrom-Json
}

<#
    .Synopsis
    Searches all targets in the given code model configuration for the named source file and returns its compile
    group information.
 
    .Outputs
    A PSCustomObject with Language, Fragments, Includes, Defines, and BuildDir properties, or $null if not found.
#>

function GetCompileInfoForSource {
    param(
        $CodeModel,
        [string] $BinaryDirectory,
        [string] $Configuration,
        [string] $SourceFilePath
    )
    $ReplyDir = Get-CMakeBuildCodeModelDirectory $BinaryDirectory
    $SourceDir = $CodeModel.paths.source

    $CodeModelConfig = if ($Configuration) {
        $CodeModel.configurations | Where-Object { $_.name -eq $Configuration }
    } else {
        $CodeModel.configurations[0]
    }

    foreach ($TargetRef in $CodeModelConfig.targets) {
        $TargetJson = Get-Content (Join-Path -Path $ReplyDir -ChildPath $TargetRef.jsonFile) | ConvertFrom-Json
        foreach ($Source in (Get-MemberValue $TargetJson 'sources' -Or @())) {
            $CompileGroupIndex = Get-MemberValue $Source 'compileGroupIndex'
            if ($null -eq $CompileGroupIndex) {
                continue
            }

            $RawSourcePath = if ([System.IO.Path]::IsPathRooted($Source.path)) {
                $Source.path
            } else {
                Join-Path -Path $SourceDir -ChildPath $Source.path
            }
            $FullSourcePath = [System.IO.Path]::GetFullPath($RawSourcePath)

            if ($FullSourcePath -ieq $SourceFilePath) {
                $CompileGroup = $TargetJson.compileGroups[$CompileGroupIndex]
                $RawBuildDir = Join-Path -Path $CodeModel.paths.build -ChildPath $TargetJson.paths.build
                return [PSCustomObject]@{
                    Language  = $CompileGroup.language
                    Fragments = Get-MemberValue $CompileGroup 'compileCommandFragments' -Or @()
                    Includes  = Get-MemberValue $CompileGroup 'includes' -Or @()
                    Defines   = Get-MemberValue $CompileGroup 'defines' -Or @()
                    BuildDir  = [System.IO.Path]::GetFullPath($RawBuildDir)
                }
            }
        }
    }
    return $null
}

<#
    .Synopsis
    Parses MSVC /showIncludes output lines into a flat list of include nodes with Depth and Path properties.
 
    MSVC format: "Note: including file: <N spaces><path>"
    where N spaces = include depth (1 = directly included).
#>

function ParseMSVCIncludes {
    param(
        [string[]] $Output
    )
    foreach ($Line in $Output) {
        if ($Line -match '^Note: including file:( +)(.+)$') {
            [PSCustomObject]@{
                Depth = $Matches[1].Length
                Path  = $Matches[2].TrimEnd()
            }
        }
    }
}

<#
    .Synopsis
    Parses Clang -H output lines into a flat list of include nodes with Depth and Path properties.
 
    Clang format: "<N dots> <path>" where N dots = include depth (1 = directly included).
#>

function ParseClangIncludes {
    param(
        [string[]] $Output
    )
    foreach ($Line in $Output) {
        if ($Line -match '^(\.+) (.+)$') {
            [PSCustomObject]@{
                Depth = $Matches[1].Length
                Path  = $Matches[2].TrimEnd()
            }
        }
    }
}