Saturday, January 21, 2012

Standalone StyleCop : a rough draft

Unlike FxCop, the StyleCop tool out of the box only comes with Visual Studio and MSBuild integration; there is no stand-alone command-line tool. Still, as it's now open-source, we can look at the workings of the MSBuild task and see how to drive it directly.

Here is a proof-of-concept PowerShell script which can be used as the basis for a proper command-line tool or script; and as a preliminary "how to" to write unit tests for your rules. Well, they aren't going to be proper unit tests, since they'd have to touch the file system, but they can crawl over test source files in a dummy project in the StyleCop rule solution.

# Load StyleCop
$path = "$($env:ProgramFiles)\StyleCop 4.5\StyleCop.dll"
Add-Type -Path $path
# arguments that must be non-null, non-empty
$settings = "$($env:UserProfile)\Documents\Visual Studio 2008\Projects\TestBed\Settings.StyleCop"
$here = Get-Location
$outputFile = Join-Path $here "output.xml"
# UNTESTED : the empty array should be filled with a list of directories containing
# StyleCop add-ins that aren't in or beneath the StyleCop install folder
$console = new-object StyleCop.StyleCopConsole -arg @($settings, $false, $outputFile, [string[]]@(), $true)
# Usual test case #defines
$defines = [string[]]@("DEBUG", "CODE_ANALYSIS")
$configuration = new-object StyleCop.Configuration -arg (,$defines)
$project = "$($env:UserProfile)\Documents\Visual Studio 2008\Projects\TestBed\TestBed.csproj"
$codeproject = new-object StyleCop.CodeProject -Arg @($project.GetHashCode(), $project, $configuration)
[StyleCop.CodeProject[]] $projects = ,$codeproject
## TODO -- get source files from project, do AddSourceCode for each file not excluded from StyleCop
$source = "$($env:UserProfile)\Documents\Visual Studio 2008\Projects\TestBed\Class1.cs"
$console.Core.Environment.AddSourceCode($codeproject, $source, $null) | Out-Null
# These are messages suitable for MSBuild e.g. "Output: (Low) Pass 1: Class1.cs"
$outputHandler = {
$e = $event.SourceEventArgs
$importance = $e.Importance
$message = $e.Output
$colour = "Green"
if ($importance -eq [Microsoft.Build.Framework.MessageImportance]::Low) { $colour = "Gray" }
if ($importance -eq [Microsoft.Build.Framework.MessageImportance]::High) { $colour = "Red" }
Write-Host "Output: ($importance) $message" -Back White -Fore $colour
}
# These are the interesting bits, which also go into the XML file e.g.
# Violation SA1200 in ...\Class1.cs (1)
# All using directives must be placed inside of the namespace.
$violationHandler = {
$e = $event.SourceEventArgs
if ($e.SourceCode -and $e.SourceCode.Path) {
$file = $e.SourceCode.Path;
} else {
if ($e.Element -and $e.Element.Document -and $e.Element.Document.SourceCode -and $e.Element.Document.SourceCode.Path)
{
$file = $e.Element.Document.SourceCode.Path;
}
}
$colour = "Black"
if ($e.Warning) { $colour = "Blue" }
Write-Host "Violation $($e.Violation.Rule.CheckId) in $file ($($e.LineNumber))`r`n`t$($e.message)" -Back White -Fore $colour
}
try {
Register-ObjectEvent -InputObject $console -EventName OutputGenerated -SourceIdentifier "Console.OutputGenerated" -Action $outputHandler | Out-Null
Register-ObjectEvent -InputObject $console -EventName ViolationEncountered -SourceIdentifier "Console.ViolationEncountered" -Action $violationHandler | Out-Null
$Console.Start($projects, $true) | Out-Null
} catch [System.Exception] {
Write-Host $_.Exception.ToString()
} finally {
Unregister-Event "Console.OutputGenerated"
Unregister-Event "Console.ViolationEncountered"
}
## Could dump the XML format output here, like this; or run an XSLT report generation over it
##$values = Get-Content $outputFile
##Write-Host $values$outputFile
view raw gistfile1.ps1 hosted with ❤ by GitHub

And that's all there is to it, really.


No comments :