Skip to content

Commit

Permalink
New PSSA custom rule for opening braces
Browse files Browse the repository at this point in the history
- Add Script Analyzer custom rule to test functions and statements so that
  opening braces are set according to the style guideline (issue PowerShell#27).
  • Loading branch information
johlju committed Aug 17, 2017
1 parent 8ebcda4 commit 903d622
Show file tree
Hide file tree
Showing 3 changed files with 284 additions and 41 deletions.
317 changes: 276 additions & 41 deletions DscResource.AnalyzerRules/DscResource.AnalyzerRules.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -64,69 +64,304 @@ function Measure-ParameterBlockParameterAttribute
if ($parameterAst.Attributes.TypeName.FullName -notcontains 'parameter')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterAttributeMissing, `
$parameterAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterAttributeMissing, `
$parameterAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
}
elseif ($parameterAst.Attributes[0].TypeName.FullName -ne 'parameter')
elseif ($parameterAst.Attributes[0].Typename.FullName -ne 'parameter')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterAttributeWrongPlace, `
$parameterAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning',`
$null
)
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterAttributeWrongPlace, `
$parameterAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
}
elseif ($parameterAst.Attributes[0].TypeName.FullName -cne 'Parameter')
elseif ($parameterAst.Attributes[0].Typename.FullName -cne 'Parameter')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterAttributeLowerCase, `
$parameterAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
} # if
} # foreach parameter
} # foreach function

return $results
}
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 -ScriptBlockAst $ScriptBlockAst
.INPUTS
[System.Management.Automation.Language.ScriptBlockAst]
.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.ScriptBlockAst]
$ScriptBlockAst
)

Process
{
$results = @()

try
{
$diagnosticRecordType = 'Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord'

$findAllFunctionsFilter = {
$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]
}

[System.Management.Automation.Language.Ast[]] $functionsAst = $ScriptBlockAst.FindAll( $findAllFunctionsFilter, $true )

foreach ($functionAst in $functionsAst)
{
<#
Remove carriage return since the file is different depending if it's run in
AppVeyor or locally. Locally it contains both '\r\n', but when cloned in
AppVeyor it only contain '\n'.
#>
$functionExtentTextWithNewLine = $functionAst.Extent -replace '\r', ''

$functionExtentRows = $functionExtentTextWithNewLine -split '\n'

if ($functionExtentRows.Count)
{
# Check so that an opening brace does not exist on the same line as the function name.
if ($functionExtentRows[0] -match '\{')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.FunctionOpeningBraceNotOnSameLine, `
$functionAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
} # if
} # if

if ($functionExtentRows.Count -ge 2)
{
# Check so that an opening brace is followed by a new line.
if ($functionExtentRows[1] -match '\{.+')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.FunctionOpeningBraceShouldBeFollowedByNewLine, `
$functionAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
} # if
} # if

if ($functionExtentRows.Count -ge 3)
{
# Check so that an opening brace is followed by only one new line.
if ($functionExtentRows[2].Trim() -eq '')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.FunctionOpeningBraceShouldBeFollowedByOnlyOneNewLine, `
$functionAst.Extent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
} # if
} # if
}

return $results
}
catch
{
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}

<#
.SYNOPSIS
Validates the statement block braces and new lines around braces.
.DESCRIPTION
Each statement should have the opening brace on a separate line. Also, the
opening brace should be followed by a new line.
.EXAMPLE
Measure-StatementBlockBraces -ScriptBlockAst $ScriptBlockAst
.INPUTS
[System.Management.Automation.Language.ScriptBlockAst]
.OUTPUTS
[Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]]
.NOTES
None
#>
function Measure-StatementBlockBraces
{
[CmdletBinding()]
[OutputType([Microsoft.Windows.Powershell.ScriptAnalyzer.Generic.DiagnosticRecord[]])]
Param
(
[Parameter(Mandatory = $true)]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.Language.ScriptBlockAst]
$ScriptBlockAst
)

Process
{
$results = @()

try
{
$diagnosticRecordType = 'Microsoft.Windows.PowerShell.ScriptAnalyzer.Generic.DiagnosticRecord'

$findAllFunctionsFilter = {
$args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst]
}

[System.Management.Automation.Language.Ast[]] $functionsAst = $ScriptBlockAst.FindAll( $findAllFunctionsFilter, $true )

foreach ($functionAst in $functionsAst)
{
$findAllStatementBlockFilter = {
$args[0] -is [System.Management.Automation.Language.StatementBlockAst]
}

[System.Management.Automation.Language.Ast[]] $statementBlocksAst = $functionAst.FindAll( $findAllStatementBlockFilter, $true )
if ($statementBlocksAst)
{
$statementParentExtents = $statementBlocksAst.Parent.Extent

foreach ($statementParentExtent in $statementParentExtents)
{
<#
Remove carriage return since the file is different depending if it's run in
AppVeyor or locally. Locally it contains both '\r\n', but when cloned in
AppVeyor it only contain '\n'.
#>
$statementParentExtentTextWithNewLine = $statementParentExtent.Text -replace '\r', ''

$statementParentExtentRows = $statementParentExtentTextWithNewLine -split '\n'

if ($statementParentExtentRows.Count)
{
# Check so that an opening brace does not exist on the same line as the statement.
if ($statementParentExtentRows[0] -match '\{')
{

$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterAttributeLowerCase, `
$parameterAst.Extent, `
$localizedData.StatementOpeningBraceNotOnSameLine, `
$statementParentExtent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
} # if
} # if
} # if
} # foreach statement parent

if ($parameterAst.Attributes.NamedArguments.ArgumentName -eq 'Mandatory')
$statementExtents = $statementBlocksAst.Extent

foreach ($statementExtent in $statementExtents)
{
if ($parameterAst.Attributes[0].NamedArguments.Extent.Text -match '\$false')
<#
Remove carriage return since the file is different depending if it's run in
AppVeyor or locally. Locally it contains both '\r\n', but when cloned in
AppVeyor it only contain '\n'.
#>
$statementExtentTextWithNewLine = $statementExtent.Text -replace '\r', ''

$statementExtentRows = $statementExtentTextWithNewLine -split '\n'

if ($statementExtentRows.Count)
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockNonMandatoryParameterMandatoryAttributeWrongFormat, `
$parameterAst.Extent, `
# Check so that an opening brace is followed by a new line.
if ($statementExtentRows[0] -match '\{.+')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.StatementOpeningBraceShouldBeFollowedByNewLine, `
$statementExtent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
}
elseif ($parameterAst.Attributes.NamedArguments.Extent.Text -cne 'Mandatory = $true')
)
} # if
} # if

if ($statementExtentRows.Count -ge 2)
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.ParameterBlockParameterMandatoryAttributeWrongFormat, `
$parameterAst.Extent, `
# Check so that an opening brace is followed by only one new line.
if ($statementExtentRows[1].Trim() -eq '')
{
$results += New-Object `
-Typename $diagnosticRecordType `
-ArgumentList @(
$localizedData.StatementOpeningBraceShouldBeFollowedByOnlyOneNewLine, `
$statementExtent, `
$PSCmdlet.MyInvocation.InvocationName, `
'Warning', `
$null
)
}
} # if

} # foreach parameter
} # if
} # if
} # foreach statement
} # if
} # foreach function

return $results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@ ParameterBlockParameterAttributeLowerCase = The [Parameter()] attribute must st
ParameterBlockParameterAttributeWrongPlace = The [Parameter()] attribute must be the first attribute of each parameter. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#correct-format-for-parameter-block
ParameterBlockParameterMandatoryAttributeWrongFormat = Mandatory parameters must use the correct format [Parameter(Mandatory = $true)] for the mandatory attribute. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#correct-format-for-parameter-block
ParameterBlockNonMandatoryParameterMandatoryAttributeWrongFormat = Non-mandatory parameters must use the correct format [Parameter()] for the parameter attribute. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#correct-format-for-parameter-block
FunctionOpeningBraceNotOnSameLine = Functions should not have the open brace on the same line as the function name. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#one-newline-before-braces
FunctionOpeningBraceShouldBeFollowedByNewLine = Opening brace on function should be followed by a new line. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#one-newline-after-opening-brace
FunctionOpeningBraceShouldBeFollowedByOnlyOneNewLine = Opening brace on functions should only be followed by one new line. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#one-newline-after-opening-brace
StatementOpeningBraceNotOnSameLine = Statements should not have the open brace on the same line as the statement. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#one-newline-before-braces
StatementOpeningBraceShouldBeFollowedByNewLine = Opening brace on statements should be followed by a new line. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#one-newline-after-opening-brace
StatementOpeningBraceShouldBeFollowedByOnlyOneNewLine = Opening brace on statements should only be followed by one new line. See https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md#one-newline-after-opening-brace
'@
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,8 @@ Invoke-AppveyorAfterTestTask `
* Updated AppVeyor code so that common tests and unit tests is run on the working
branch's tests. This is to be able to test changes to tests in pull requests
without having to merge the pull request before seeing the result.
* Add Script Analyzer custom rule to test functions and statements so that
opening braces are set according to the style guideline ([issue #27](https://github.com/PowerShell/DscResource.Tests/issues/27)).

### 0.2.0.0

Expand Down

0 comments on commit 903d622

Please sign in to comment.