SelectStringExceptComment.psm1

enum MatchType {
    AllMatches = 0
    Match = 1
    NotMatch = 2
}

function exceptMultiLineComment () {
    param(
        [ValidateNotNullOrEmpty()]
        $GrepData,
        [ValidateNotNullOrEmpty()]
        [string]$Extension
    )
    [int]$commentNest = 0
    $result = @()
    for ($i = 0; $i -lt $GrepData.Length; $i++) {
        if ($Extension -ne '.py' -or $commentNest -eq 0) {
            if ($GrepData[$i].Line -match $multiLineCommentDictionary.Item($Extension)[0]) {
                $commentNest += 1
                if ($Extension -eq '.py') {
                    continue
                }
            }
        }
        if ($commentNest -ne 0) {
            if ($GrepData[$i].Line -match $multiLineCommentDictionary.Item($Extension)[1]) {
                $commentNest -= 1
                continue
            }
        }
        if ($commentNest -ne 0) {
            continue
        }
        $result += $GrepData[$i]
    }
    return $result
}

function Select-StringExceptComment () {
    <#
    .SYNOPSIS
        コメント以外の文字列を検索します。
    .DESCRIPTION
        指定パスの拡張子からコメント種類を判定し、
        コメント行・ブランク行を除外した上で文字列検索を行います。
        ただし、同一行内にてネスト構造になっているコメントがある場合は正常に動作しません。
        また、コメント行とコーディングが同一ラインに存在する場合、
        コメント行として除外される点にもご注意ください。
        足りない拡張子は設定を追加してください。
    .EXAMPLE
        PS C:\> Get-ChildItem ./ | Select-StringExceptComment -Pattern kore, sore
    .EXAMPLE
        PS C:\> Get-ChildItem ./ | Select-StringExceptComment -Pattern hoge -MatchType NotMatch
    .EXAMPLE
        PS C:\> Get-ChildItem ./ |
                    Select-StringExceptComment -Pattern hoge -SimpleMatch -CaseSensitive -Encoding ([System.Text.Encoding]::Default)
    .PARAMETER LiteralPath
        対象となるフォルダパス
    .PARAMETER Pattern
        検索する単語。複数指定可
    .PARAMETER MatchType
        AllMatches, Match, NotMatch。default = Match
    .PARAMETER SimpleMatch
        正規表現を使用しない
    .PARAMETER CaseSensitive
        大文字小文字を区別して判定する
    .PARAMETER Encoding
        System.Text.Encoding。default = UTF8
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias("Path")]
        [ValidateNotNullOrEmpty()]
        [string[]]$LiteralPath,
        [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [String[]]$Pattern,
        [Alias("Match")]
        [MatchType]$MatchType = [MatchType]::Match,
        [Alias("Simple")]
        [switch]$SimpleMatch,
        [Alias("Case")]
        [switch]$CaseSensitive,
        [Alias("Enc")]
        [System.Text.Encoding]$Encoding = [System.Text.Encoding]::UTF8
    )
    begin {
        $multiLineCommentDictionary = 
        @{  '.applescript' = [string[]]@('(\*', '\*)')
            '.aspx'        = [string[]]@('<%--', '--%>')
            '.c'           = [string[]]@('/\*', '\*/')
            '.cpp'         = [string[]]@('/\*', '\*/')
            '.cs'          = [string[]]@('/\*', '\*/')
            '.css'         = [string[]]@('/\*', '\*/')
            '.fs'          = [string[]]@('(\*', '\*)')
            '.go'          = [string[]]@('/\*', '\*/')
            '.groovy'      = [string[]]@('/\*', '\*/')
            '.hs'          = [string[]]@('{-', '-}')
            '.html'        = [string[]]@('<!--', '-->')
            '.jar'         = [string[]]@('/\*', '\*/')
            '.java'        = [string[]]@('/\*', '\*/')
            '.js'          = [string[]]@('/\*', '\*/')
            '.lhs'         = [string[]]@('{-', '-}')
            '.m'           = [string[]]@('/\*', '\*/')
            '.ps1'         = [string[]]@('<#', '#>')
            '.psm1'        = [string[]]@('<#', '#>')
            '.php'         = [string[]]@('/\*', '\*/')
            '.pl'          = [string[]]@('=comment', '=cut')
            '.py'          = [string[]]@("(`'`'`'|`"`"`")", "(`'`'`'|`"`"`")")
            '.rb'          = [string[]]@('=begin', '=end')
            '.scala'       = [string[]]@('/\*', '\*/')
            '.scpt'        = [string[]]@('(\*', '\*)')
            '.scptd'       = [string[]]@('(\*', '\*)')
            '.swift'       = [string[]]@('/\*', '\*/')
            '.ts'          = [string[]]@('/\*', '\*/')
        }

        $singleLineCommentDictionary = 
        @{  '.applescript' = [string[]]@("--")
            '.bat'         = [string[]]@('\s*REM ', '^\s*::')
            '.c'           = [string[]]@('//')
            '.cpp'         = [string[]]@('//')
            '.cs'          = [string[]]@('//')
            '.fs'          = [string[]]@('//')
            '.go'          = [string[]]@('//')
            '.groovy'      = [string[]]@('//')
            '.hs'          = [string[]]@('--')
            '.jar'         = [string[]]@('//')
            '.java'        = [string[]]@('//')
            '.js'          = [string[]]@('//')
            '.lhs'         = [string[]]@('--')
            '.m'           = [string[]]@('//')
            '.php'         = [string[]]@('//', '#')
            '.pl'          = [string[]]@('#')
            '.pm'          = [string[]]@('#')
            '.psm1'        = [string[]]@('#')
            '.ps1'         = [string[]]@('#')
            '.py'          = [string[]]@('#')
            '.rb'          = [string[]]@('#')
            '.scala'       = [string[]]@('//')
            '.scpt'        = [string[]]@("--")
            '.scptd'       = [string[]]@("--")
            '.sh'          = [string[]]@('#')
            '.sql'         = [string[]]@('--')
            '.swift'       = [string[]]@('//')
            '.ts'          = [string[]]@('//')
            '.vb'          = [string[]]@("`'")
            '.vbs'         = [string[]]@("`'", "\s*Rem ")
        }
        
        $grepCommand = 'Select-String -InputObject $_ -Pattern $Pattern -Encoding $Encoding'
        switch ($MatchType) {
            'AllMatches' {$grepCommand += " -AllMatches"; break}
            'NotMatch' {$grepCommand += " -NotMatch"; break}        
        }
        if ($SimpleMatch) {$grepCommand += " -SimpleMatch"}
        if ($CaseSensitive) {$grepCommand += " -CaseSensitive"}
    }
    process {
        foreach ($path in $LiteralPath) {
            $extension = [System.IO.Path]::GetExtension($path)
            $result = Get-Item -Path $path | 
                Select-String -Pattern '^\s*$' -NotMatch -Encoding $Encoding
            if ($multiLineCommentDictionary.Keys -contains $extension) {
                $result = exceptMultiLineComment -GrepData $result -Extension $extension
            }
            if ($singleLineCommentDictionary.Keys -contains $extension) {
                $result = $result | 
                    Select-String -Pattern $singleLineCommentDictionary.Item($extension) -Encoding $Encoding -NotMatch
            }
            $result = $result | 
                ForEach-Object {Invoke-Expression -Command $grepCommand}
            Write-Output $result
        }
    }
}

Export-ModuleMember -Function Select-StringExceptComment