Monday, January 23, 2012

HTML reporting for StyleCop

Based on the outputs from the earlier script; and using an appearance inspired by the output from the XSL report approach from codecampserver; but with the source in-lined into the report, and violation messages inserted after the affected lines. Each file (and the nested violation messages) are hidden by default, and can be expanded by clicking the file header or the source marked as violation (where the hand cursor shows):

N.B. Defined as it is within a PowerShell here-string, the jQuery script needs its $ symbols to be escaped to keep the PowerShell parser happy.



You will need to adjust the script to find the appropriate StyleCop output files from your build process, and create suitably named/located reports based on those files.

Sunday, January 22, 2012

Garden rubbish

Apart from a brief spell around last weekend, and into the start of the past week, where temperatures driving to work were in the -3C to -5C range, winter hasn't really arrived -- we've just had an extended autumn.

The cold finally did for the the antirrhinum flowers, and the last signs of life in the sunflowers, which are still serving as bird-feeders; but we still have overwintering escholzias; self-sown poached-egg plants from last summer's flowering already in bloom, primroses since before the end of last year, and now snowdrops and the first of the early crocuses.

I have at least been able to tend to a number of the tidy-up chores : pruning roses, fuchsias and apple trees; clearing out the straw from various dead annuals, and the last year's growth from the crocosmias and lemon balm.

And of course there are weeds already -- including a fine old crop of winter wheat from the porridge-like mass of spilt grain out of the bird feeder, as well as the usual stubborn perennials, misplaced self-seedings and the like. Which all means that the green bin is continually being put out full, even in the putative low season.

If it were drier, there'd even be grass clippings, as the mild wet weather is encouraging the lawn already!


Adding the missing pieces to the Standalone StyleCop script

Rather than post the whole ~200 lines, just the salient bits so that the original can be tidied up (but without obscuring everything with obsessive error handling). Start by giving it parameters like

<#
.SYNOPSIS
Stand-alone stylecop runner.
.DESCRIPTION
Runs StyleCop over a project or a solution
.NOTES
File Name : Run-StyleCop.ps1
Requires : PowerShell Version 2.0
.PARAMETER FilesToScan
Project or Solution file
.PARAMETER StyleCopFolder
To override the heuristic looking in $env:ProgramFiles for the latest version
e.g. for StyleCop in source control
.PARAMETER OnlyBuildIntegrated
Only scan project files which include <Import Project="something with 'stylecop' in it" />
#>
param (
[Parameter(Mandatory = $true)] [string] $FilesToScan,
[string] $StyleCopFolder,
[switch] $OnlyBuildIntegrated)
Find StyleCop in the default install location by
if (-not $StyleCopFolder) {
$styleCopFolder = (dir "$($env:programFiles)*\stylecop*" | Sort-object LastWriteTimeUtc | Select-Object -Last 1).FullName
}
view raw gistfile1.ps1 hosted with ❤ by GitHub

Find StyleCop in the default install location by


if (-not $StyleCopFolder) {
    $styleCopFolder =  (dir "$($env:programFiles)*\stylecop*" | Sort-object LastWriteTimeUtc | Select-Object -Last 1).FullName
}

Get the list of projects by either just the project, or using a regex on the project file from here: http://bytes.com/topic/c-sharp/answers/483959-enumerate-projects-solution

if ($FilesToScan -like "*.csproj") {
$projectList = ,$FilesToScan
} else {
if ($FilesToScan -like "*.sln") {
$solutionDir = Split-Path $FilesToScan
$matchProjectNameRegex =
'^Project\("(?<PROJECTTYPEGUID>.*)"\)\s*=\s* "(?<PROJECTNAME>.*)"\s*,\s*"(?<PROJECTRELATIVEPATH>.*)"\s*,\s*"(?<PROJECTGUID>.*)"$'
# or we could filter on project guid being {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
$projectList = @(Get-Content $FilesToScan | % {
$matches = $null; $_ -match $matchProjectNameRegex | Out-Null; $matches.PROJECTRELATIVEPATH } | ? {
$_ -like "*.csproj" } | % { Join-Path $solutionDir $_ } )
} else {
Write-Error "$FilesToScan isn't a solution file or a C# project"
}
}
# Check if required for a StyleCop target
if ($OnlyBuildIntegrated) {
$projectList = @($projectList | ? {
[xml]$content = Get-Content $_
$imports = @($content.Project.Import)
$flagged = (@($imports | % { $_.Project } | ? { $_ -like "*stylecop*" })).Length
if ($flagged -eq 0) { Write-Host "Skipping project $_" }
($flagged -gt 0)
})}
view raw gistfile1.ps1 hosted with ❤ by GitHub

Find a Settings.StyleCop file by looking at the project folder and up, defaulting to one in the StyleCop folder; and then scan the C# files of interest by

[xml]$content = Get-Content $projectFile
# All C# files
$cs = @($content.Project.ItemGroup | % { $_.Compile } | ? {$_})
# Filter excluded files; get absolute paths
$projectDir = Split-Path $_
$cs = @($cs | ? { -not (($cs[0].ExcludeFromStyleCop -like "true") -or ($cs[0].ExcludeFromSourceAnalysis -like "true"))
} | % { Join-Path $projectDir $_.Include })
$cs | % { $console.Core.Environment.AddSourceCode($codeproject, $_, $null) | Out-Null }
view raw gistfile1.ps1 hosted with ❤ by GitHub

and customise the output file as

$name = (Split-Path $projectFile -Leaf).Replace(".csproj", ".StyleCop.xml")
$outputXml = Join-Path (Get-Location) $name
view raw gistfile1.ps1 hosted with ❤ by GitHub

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.