DscResource.Tests/DscResource.AnalyzerRules/DscResource.AnalyzerRules.psm1

#Requires -Version 4.0

# Import helper module
Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath 'DscResource.AnalyzerRules.Helper.psm1')

# Import Localized Data
Import-LocalizedData -BindingVariable localizedData

$script:diagnosticRecordType = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord]
$script:diagnosticRecord = @{
    Message  = ''
    Extent   = $null
    RuleName = $null
    Severity = 'Warning'
}

<#
    .SYNOPSIS
        Validates the [Parameter()] attribute for each parameter.
 
    .DESCRIPTION
        All parameters in a param block must contain a [Parameter()] attribute
        and it must be the first attribute for each parameter and must start with
        a capital letter P.
 
    .EXAMPLE
        Measure-ParameterBlockParameterAttribute -ParameterAst $parameterAst
 
    .INPUTS
        [System.Management.Automation.Language.ParameterAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
    .NOTES
        None
#>

function Measure-ParameterBlockParameterAttribute
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ParameterAst]
        $ParameterAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $ParameterAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName
        [System.Boolean] $inAClass = Test-IsInClass -Ast $ParameterAst

        <#
            If we are in a class the parameter attributes are not valid in Classes
            the ParameterValidation attributes are however
        #>

        if (!$inAClass)
        {
            if ($ParameterAst.Attributes.TypeName.FullName -notcontains 'parameter')
            {
                $script:diagnosticRecord['Message'] = $localizedData.ParameterBlockParameterAttributeMissing

                $script:diagnosticRecord -as $script:diagnosticRecordType
            }
            elseif ($ParameterAst.Attributes[0].TypeName.FullName -ne 'parameter')
            {
                $script:diagnosticRecord['Message'] = $localizedData.ParameterBlockParameterAttributeWrongPlace

                $script:diagnosticRecord -as $script:diagnosticRecordType
            }
            elseif ($ParameterAst.Attributes[0].TypeName.FullName -cne 'Parameter')
            {
                $script:diagnosticRecord['Message'] = $localizedData.ParameterBlockParameterAttributeLowerCase

                $script:diagnosticRecord -as $script:diagnosticRecordType
            }
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates use of the Mandatory named argument within a Parameter attribute.
 
    .DESCRIPTION
        If a parameter attribute contains the mandatory attribute the
        mandatory attribute must be formatted correctly.
 
    .EXAMPLE
        Measure-ParameterBlockMandatoryNamedArgument -NamedAttributeArgumentAst $namedAttributeArgumentAst
 
    .INPUTS
        [System.Management.Automation.Language.NamedAttributeArgumentAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
    .NOTES
        None
#>

function Measure-ParameterBlockMandatoryNamedArgument
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.NamedAttributeArgumentAst]
        $NamedAttributeArgumentAst
    )

    try
    {
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName
        [System.Boolean] $inAClass = Test-IsInClass -Ast $NamedAttributeArgumentAst

        <#
            Parameter Attributes are not valid in classes, and DscProperty does
            not use the (Mandatory = $true) format just DscProperty(Mandatory)
        #>

        if (!$inAClass)
        {
            if ($NamedAttributeArgumentAst.ArgumentName -eq 'Mandatory')
            {
                $script:diagnosticRecord['Extent'] = $NamedAttributeArgumentAst.Extent

                if ($NamedAttributeArgumentAst)
                {
                    $invalidFormat = $false
                    try
                    {
                        $value = $NamedAttributeArgumentAst.Argument.SafeGetValue()
                        if ($value -eq $false)
                        {
                            $script:diagnosticRecord['Message'] = $localizedData.ParameterBlockNonMandatoryParameterMandatoryAttributeWrongFormat

                            $script:diagnosticRecord -as $script:diagnosticRecordType
                        }
                        elseif ($NamedAttributeArgumentAst.Argument.VariablePath.UserPath -cne 'true')
                        {
                            $invalidFormat = $true
                        }
                        elseif ($NamedAttributeArgumentAst.ArgumentName -cne 'Mandatory')
                        {
                            $invalidFormat = $true
                        }
                    }
                    catch
                    {
                        $invalidFormat = $true
                    }

                    if ($invalidFormat)
                    {
                        $script:diagnosticRecord['Message'] = $localizedData.ParameterBlockParameterMandatoryAttributeWrongFormat

                        $script:diagnosticRecord -as $script:diagnosticRecordType
                    }
                }
            }
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the function block braces and new lines around braces.
 
    .DESCRIPTION
        Each function should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
 
    .EXAMPLE
        Measure-FunctionBlockBraces -FunctionDefinitionAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.FunctionDefinitionAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-FunctionBlockBraces
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.FunctionDefinitionAst]
        $FunctionDefinitionAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $FunctionDefinitionAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $FunctionDefinitionAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.FunctionOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.FunctionOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.FunctionOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the if-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each if-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The if statement should also be in all lower case.
        The else and elseif statements are not currently checked.
 
    .EXAMPLE
        Measure-IfStatement -IfStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.IfStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-IfStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.IfStatementAst]
        $IfStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $IfStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        <#
            This removes the clause from if-statement, so it ends up an empty if().
            This is to resolve issue #238, when the clause spans multiple rows,
            and the first rows ends with a correct open brace. See example in
            regression test for #238.
        #>

        $extentTextWithClauseRemoved = $IfStatementAst.Extent.Text.Replace($IfStatementAst.Clauses[0].Item1.Extent.Text, '')

        $testParameters = @{
            StatementBlock = $extentTextWithClauseRemoved
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.IfStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.IfStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.IfStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'if'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the foreach-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each foreach-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The foreach statement should also be in all lower case.
 
    .EXAMPLE
        Measure-ForEachStatement -ForEachStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.ForEachStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-ForEachStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ForEachStatementAst]
        $ForEachStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $ForEachStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $ForEachStatementAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.ForEachStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.ForEachStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.ForEachStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'foreach'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the DoUntil-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each DoUntil-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The do statement should also be in all lower case.
 
    .EXAMPLE
        Measure-DoUntilStatement -DoUntilStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.DoUntilStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-DoUntilStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.DoUntilStatementAst]
        $DoUntilStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $DoUntilStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $DoUntilStatementAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.DoUntilStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.DoUntilStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.DoUntilStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'do'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the DoWhile-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each DoWhile-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The do statement should also be in all lower case.
 
    .EXAMPLE
        Measure-DoWhileStatement -DoWhileStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.DoWhileStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-DoWhileStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.DoWhileStatementAst]
        $DoWhileStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $DoWhileStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $DoWhileStatementAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.DoWhileStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.DoWhileStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.DoWhileStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'do'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the while-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each while-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The while statement should also be in all lower case.
 
    .EXAMPLE
        Measure-WhileStatement -WhileStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.WhileStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-WhileStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.WhileStatementAst]
        $WhileStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $WhileStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $WhileStatementAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.WhileStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.WhileStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.WhileStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'while'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the for-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each for-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The for statement should also be in all lower case.
 
    .EXAMPLE
        Measure-ForStatement -ForStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.ForStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-ForStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.ForStatementAst]
        $ForStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $ForStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $ForStatementAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.ForStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.ForStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.ForStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'for'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the switch-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each switch-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The switch statement should also be in all lower case.
 
    .EXAMPLE
        Measure-SwitchStatement -SwitchStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.SwitchStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-SwitchStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.SwitchStatementAst]
        $SwitchStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $SwitchStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $SwitchStatementAst.Extent
        }

        <#
            Must use an else block here, because otherwise, if there is a
            switch-clause that is formatted wrong it will hit on that
            and return the wrong rule message.
        #>

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.SwitchStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
        elseif (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.SwitchStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.SwitchStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'switch'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the try-statement block braces and new lines around braces.
 
    .DESCRIPTION
        Each try-statement should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The try statement should also be in all lower case.
 
    .EXAMPLE
        Measure-TryStatement -TryStatementAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.TryStatementAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-TryStatement
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.TryStatementAst]
        $TryStatementAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $TryStatementAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $TryStatementAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.TryStatementOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.TryStatementOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.TryStatementOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'try'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the catch-clause block braces and new lines around braces.
 
    .DESCRIPTION
        Each catch-clause should have the opening brace on a separate line.
        Also, the opening brace should be followed by a new line.
        The catch statement should also be in all lower case.
 
    .EXAMPLE
        Measure-CatchClause -CatchClauseAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.CatchClauseAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-CatchClause
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.CatchClauseAst]
        $CatchClauseAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $CatchClauseAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $CatchClauseAst.Extent
        }

        if (Test-StatementOpeningBraceOnSameLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.CatchClauseOpeningBraceNotOnSameLine
            $script:diagnosticRecord -as $diagnosticRecordType
        }

        if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.CatchClauseOpeningBraceShouldBeFollowedByNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.CatchClauseOpeningBraceShouldBeFollowedByOnlyOneNewLine
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if

        if (Test-StatementContainsUpperCase @testParameters)
        {
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'catch'
            $script:diagnosticRecord -as $diagnosticRecordType
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates the Class and Enum of PowerShell.
 
    .DESCRIPTION
        Each Class or Enum must be formatted correctly.
        The class or enum statement should also be in all lower case.
 
    .EXAMPLE
        Measure-TypeDefinition -TypeDefinitionAst $ScriptBlockAst
 
    .INPUTS
        [System.Management.Automation.Language.TypeDefinitionAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-TypeDefinition
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.TypeDefinitionAst]
        $TypeDefinitionAst
    )

    try
    {
        $script:diagnosticRecord['Extent'] = $TypeDefinitionAst.Extent
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $testParameters = @{
            StatementBlock = $TypeDefinitionAst.Extent
        }

        if ($TypeDefinitionAst.IsEnum)
        {
            if (Test-StatementOpeningBraceOnSameLine @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.EnumOpeningBraceNotOnSameLine
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if

            if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.EnumOpeningBraceShouldBeFollowedByNewLine
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if

            if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.EnumOpeningBraceShouldBeFollowedByOnlyOneNewLine
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if

            if (Test-StatementContainsUpperCase @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'enum'
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if
        } # if
        elseif ($TypeDefinitionAst.IsClass)
        {
            if (Test-StatementOpeningBraceOnSameLine @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.ClassOpeningBraceNotOnSameLine
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if

            if (Test-StatementOpeningBraceIsNotFollowedByNewLine @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.ClassOpeningBraceShouldBeFollowedByNewLine
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if

            if (Test-StatementOpeningBraceIsFollowedByMoreThanOneNewLine @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.ClassOpeningBraceShouldBeFollowedByOnlyOneNewLine
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if

            if (Test-StatementContainsUpperCase @testParameters)
            {
                $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f 'class'
                $script:diagnosticRecord -as $diagnosticRecordType
            } # if
        } # if
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates all keywords.
 
    .DESCRIPTION
        Each keyword should be in all lower case.
 
    .EXAMPLE
        Measure-Keyword -Token $Token
 
    .INPUTS
        [System.Management.Automation.Language.Token[]]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
   .NOTES
        None
#>

function Measure-Keyword
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.Token[]]
        $Token
    )

    try
    {
        $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

        $keywordsToIgnore = @('configuration')
        $keywordFlag = [System.Management.Automation.Language.TokenFlags]::Keyword
        $keywords = $Token.Where{ $_.TokenFlags.HasFlag($keywordFlag) -and
            $_.Kind -ne 'DynamicKeyword' -and
            $keywordsToIgnore -notcontains $_.Text
        }
        $upperCaseTokens = $keywords.Where{ $_.Text -cmatch '[A-Z]+' }

        $tokenWithNoSpace = $keywords.Where{ $_.Extent.StartScriptPosition.Line -match "$($_.Extent.Text)\(.*" }

        foreach ($item in $upperCaseTokens)
        {
            $script:diagnosticRecord['Extent'] = $item.Extent
            $script:diagnosticRecord['Message'] = $localizedData.StatementsContainsUpperCaseLetter -f $item.Text
            $script:diagnosticRecord -as $diagnosticRecordType
        }

        foreach ($item in $tokenWithNoSpace)
        {
            $script:diagnosticRecord['Extent'] = $item.Extent
            $script:diagnosticRecord['Message'] = $localizedData.OneSpaceBetweenKeywordAndParenthesis
            $script:diagnosticRecord -as $diagnosticRecordType
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

<#
    .SYNOPSIS
        Validates all hashtables.
 
    .DESCRIPTION
        Hashtables should have the correct format
 
    .EXAMPLE
        PS C:\> Measure-Hashtable -HashtableAst $HashtableAst
 
    .INPUTS
        [System.Management.Automation.Language.HashtableAst]
 
    .OUTPUTS
        [Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
 
    .NOTES
        None
#>

function Measure-Hashtable
{
    [CmdletBinding()]
    [OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.Language.HashtableAst[]]
        $HashtableAst
    )

    try
    {
        foreach ($hashtable in $HashtableAst)
        {
            # Empty hashtables should be ignored
            if ($hashtable.extent.Text -eq '@{}' -or $hashtable.extent.Text -eq '@{ }')
            {
                continue
            }

            $script:diagnosticRecord['RuleName'] = $PSCmdlet.MyInvocation.InvocationName

            $hashtableLines = $hashtable.Extent.Text -split '\n'

            # Hashtable should start with '@{' and end with '}'
            if (($hashtableLines[0] -notmatch '\s*@?{\r' -and $hashtableLines[0] -notmatch '\s*@?{$') -or
                $hashtableLines[-1] -notmatch '\s*}')
            {
                $script:diagnosticRecord['Extent'] = $hashtable.Extent
                $script:diagnosticRecord['Message'] = $localizedData.HashtableShouldHaveCorrectFormat
                $script:diagnosticRecord -as $diagnosticRecordType
            }
            else
            {
                # We alredy checked that the first line is correctly formatted. Getting the starting indentation here
                $initialIndent = ([regex]::Match($hashtable.Extent.StartScriptPosition.Line, '(\s*)')).Length
                $expectedLineIndent = $initialIndent + 5

                foreach ($keyValuePair in $hashtable.KeyValuePairs)
                {
                    if ($keyValuePair.Item1.Extent.StartColumnNumber -ne $expectedLineIndent)
                    {
                        $script:diagnosticRecord['Extent'] = $hashtable.Extent
                        $script:diagnosticRecord['Message'] = $localizedData.HashtableShouldHaveCorrectFormat
                        $script:diagnosticRecord -as $diagnosticRecordType
                        break
                    }
                }
            }
        }
    }
    catch
    {
        $PSCmdlet.ThrowTerminatingError($PSItem)
    }
}

Export-ModuleMember -Function Measure-*