Private/Generator/Get-OpenApiGenCommandName.ps1

function Get-OpenApiGenCommandName {
    <#
    .SYNOPSIS
        Works out the Verb-Noun command name for one operation, before collisions are resolved.

    .DESCRIPTION
        1. x-ps-name (a full Verb-Noun with an approved verb) wins; otherwise x-ps-verb and x-ps-noun
           replace the derived verb and noun.
        2. From the operationId: the words are split, the first word is mapped to a verb (see
           Resolve-OpenApiGenVerb) and the rest is the noun. An unknown first word gives the method's
           default verb and stays in the noun.
        3. Without an operationId (or when the document model generated it, finding OA010): the method's
           default verb and the last non-parameter path segment.
        4. Noun = NounPrefix + PascalCase words with the last word singularised (the last word with
           letters, so a trailing number is skipped); only A-Z, a-z, 0-9.
        Invalid overrides are ignored with an OA040 warning finding.
        Returns { OperationId, Method, Path, Verb, Noun, BaseNoun, Name, Source, IsList, Findings }.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Operation,

        [Parameter()]
        [AllowEmptyString()]
        [string]$NounPrefix = '',

        [Parameter()]
        [switch]$OperationIdGenerated
    )

    $findings = New-Object -TypeName System.Collections.ArrayList
    $approved = Get-OpenApiGenApprovedVerb
    $method = ([string]$Operation.Method).ToUpperInvariant()
    $prefix = ConvertTo-OpenApiGenPascalCase -Word @($NounPrefix)
    if ($NounPrefix -cmatch '^[A-Za-z0-9]+$') {
        $prefix = $NounPrefix
    }

    # Path words: last segment that is not a parameter
    $pathWords = @()
    $segments = @(([string]$Operation.Path).Split('/') | Where-Object -FilterScript { $_ -ne '' -and $_ -notmatch '\{' })
    if ($segments.Count -gt 0) {
        $pathWords = @(Split-OpenApiGenWord -Value $segments[$segments.Count - 1])
    }
    if ($pathWords.Count -eq 0) {
        $pathWords = @('Root')
    }

    $source = 'operationId'
    $isList = $false
    $operationId = [string]$Operation.OperationId
    if ($OperationIdGenerated -or [string]::IsNullOrEmpty($operationId)) {
        $source = 'path'
        $verb = (Resolve-OpenApiGenVerb -Method $method).Verb
        $nounWords = $pathWords
    }
    else {
        $words = @(Split-OpenApiGenWord -Value $operationId)
        $resolved = $null
        if ($words.Count -gt 0) {
            $resolved = Resolve-OpenApiGenVerb -Word $words[0] -Method $method
        }
        if ($null -ne $resolved) {
            $verb = $resolved.Verb
            $isList = $resolved.IsList
            $nounWords = @($words | Select-Object -Skip 1)
        }
        else {
            $verb = (Resolve-OpenApiGenVerb -Method $method).Verb
            $nounWords = $words
        }
        if ($nounWords.Count -eq 0) {
            $nounWords = $pathWords
        }
    }
    # Singularise the last word that has letters ('listOrders_2' -> 'Order2')
    $nounWords = @($nounWords)
    for ($i = $nounWords.Count - 1; $i -ge 0; $i--) {
        if ($nounWords[$i] -match '\p{L}') {
            $nounWords[$i] = ConvertTo-OpenApiGenSingular -Word $nounWords[$i]
            break
        }
    }
    $baseNoun = ConvertTo-OpenApiGenPascalCase -Word $nounWords
    if ($baseNoun -eq '') {
        $baseNoun = 'Resource'
    }

    # Overrides from x-ps-verb / x-ps-noun / x-ps-name
    $extensions = $Operation.Extensions
    $overrideVerb = Get-OpenApiGenMapValue -Map $extensions -Key 'x-ps-verb'
    if ($null -ne $overrideVerb) {
        $match = @($approved | Where-Object -FilterScript { $_ -eq [string]$overrideVerb })
        if ($match.Count -gt 0) {
            $verb = $match[0]
            $source = 'x-ps-verb'
        }
        else {
            [void]$findings.Add((New-OpenApiGenFinding -Severity Warning -Code 'OA040' -Operation $Operation -PointerSuffix '/x-ps-verb' -Message "x-ps-verb '$overrideVerb' is not an approved PowerShell verb and was ignored; the command uses '$verb'."))
        }
    }
    $overrideNoun = Get-OpenApiGenMapValue -Map $extensions -Key 'x-ps-noun'
    if ($null -ne $overrideNoun) {
        $cleanNoun = [string]$overrideNoun
        if ($cleanNoun -cnotmatch '^[A-Za-z0-9]+$') {
            $cleanNoun = ConvertTo-OpenApiGenPascalCase -Value $cleanNoun
        }
        elseif ($cleanNoun.Length -gt 0) {
            $cleanNoun = $cleanNoun.Substring(0, 1).ToUpperInvariant() + $cleanNoun.Substring(1)
        }
        if ($cleanNoun -ne '') {
            $baseNoun = $cleanNoun
            $source = 'x-ps-noun'
        }
    }
    $noun = $prefix + $baseNoun

    $overrideName = Get-OpenApiGenMapValue -Map $extensions -Key 'x-ps-name'
    if ($null -ne $overrideName) {
        $nameMatch = [regex]::Match([string]$overrideName, '^([A-Za-z]+)-([A-Za-z0-9]+)$')
        $verbMatch = @()
        if ($nameMatch.Success) {
            $verbMatch = @($approved | Where-Object -FilterScript { $_ -eq $nameMatch.Groups[1].Value })
        }
        if ($verbMatch.Count -gt 0) {
            $verb = $verbMatch[0]
            $noun = $nameMatch.Groups[2].Value
            $baseNoun = $noun
            if ($prefix -ne '' -and $noun.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase) -and $noun.Length -gt $prefix.Length) {
                $baseNoun = $noun.Substring($prefix.Length)
            }
            $source = 'x-ps-name'
        }
        else {
            [void]$findings.Add((New-OpenApiGenFinding -Severity Warning -Code 'OA040' -Operation $Operation -PointerSuffix '/x-ps-name' -Message "x-ps-name '$overrideName' is not a Verb-Noun name with an approved verb and was ignored; the command uses '$verb-$noun'."))
        }
    }

    return [pscustomobject]@{
        PSTypeName  = 'Tcs.OpenApi.CommandName'
        OperationId = $Operation.OperationId
        Method      = $method
        Path        = [string]$Operation.Path
        Verb        = $verb
        Noun        = $noun
        BaseNoun    = $baseNoun
        Name        = "$verb-$noun"
        Source      = $source
        IsList      = $isList
        Findings    = $findings.ToArray()
    }
}