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.

<# 
.SYNOPSIS 
    Stylecop HTML report generator. 
    
.DESCRIPTION 
    Creates *.html from *.StyleCop.xml in the current directory 
    These are reports with pretty reporting
        
.NOTES 
    File Name  : Transform-StyleCopReport.ps1 
    Requires   : PowerShell Version 2.0
#> 
function Insert-Newline([System.Xml.XmlElement] $element) {
    $break = $element.OwnerDocument.CreateSignificantWhitespace("`r`n");
    $element.AppendChild($break) | Out-Null
}

function Make-HTML([string] $file) {
    Write-Host "Processing report $file"
    $htmlfile = $file.Replace(".StyleCop.xml", ".html")
    [xml]$raw = Get-Content $file
    [xml]$report = @"
<!DOCTYPE html>
<html>
<head>
<title>StyleCop Report</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<style>
body {position: absolute; top: 0px; width: 100%;margin:0px;min-height:100%;padding:0}
body {font-family:Verdana, Helvetica, sans-serif;font-size:70%;background:#fff;color:#000;}
h1 { padding: 10px; width: 100%; background-color: #566077; color: #fff; }
h2 { margin:10px; padding: 10px; background-color: #ffc; border: #d7ce28 1px solid;  }
h3 { margin:25px; padding: 10px; background: #b9c9fe; color: #039; border: 1px solid #aabcfe; }
h3 span {float: right;}
div { margin:25px; border: #d7ce28 1px solid; }
div div { border: none; }
pre { font-family:Consolas, Inconsolata, "Lucida Console", "Courier New", monospace;font-size:100%; padding: 0; margin:0}
pre.odd { background: #eee; }
pre.even { background: #ddf; }
span.violation { background: #ff8; }
span.index { border-right : 1px solid black }
</style>
<script type="text/javascript">
`$(function(){
  // inner first, then outer
  `$('span.violation')
    .click(function(event) {
      if (this == event.target) {
        `$(this).parent().next().toggle();
      }
    return false;
  })
  .css('cursor', 'pointer')
  .click();  
  
  `$('h3')
    .click(function(event) {
      if (this == event.target) {
        `$(this).next().toggle();
      }
    return false;
  })
  .css('cursor', 'pointer')
  .click();
});
</script>

</head>

<body>
<h1>StyleCop Report</h1>

</body>

</html>
"@
    $body = (Select-Xml -Xml $report -XPath "//body").Node
    $doc = $body.OwnerDocument
    $doc.PreserveWhitespace = $true
    Insert-Newline $body
    
    $x = $doc.CreateElement("h2", "")
    $violations = (Select-Xml -Xml $raw -XPath "//Violation")
    
    $x.InnerText = "Total Violations : $($violations.Length)"
    $body.appendChild($x) | Out-Null
    Insert-Newline $body
    
    $vfiles = @($violations | % { $_.Node.Source } | Sort-Object -Unique)
    
    $vfiles | % { 
        $path = Resolve-Path $_
        $local = (Select-Xml -Xml $raw -XPath "//Violation[@Source='$_']")

        $h = $doc.CreateElement("h3", "")
        $body.AppendChild($h) | Out-Null
        Insert-Newline $body
        
        $t = $doc.CreateTextNode("SourceFile: $path")
        $span = $doc.CreateElement("span", "")
        
        $span.InnerText = "($($local.Length) violations)"
        $h.AppendChild($t)     | Out-Null
        $h.AppendChild($span)  | Out-Null
        
        $sdiv = $doc.CreateElement("div", "")
        $body.AppendChild($sdiv) | Out-Null
                    
        $source = Get-Content $path
        
        $line = 0
        $source | % {
            $line += 1
            $pre = $doc.CreateElement("pre", "")
            $num = $line.ToString()
            while ($num.Length -lt 4) { $num = " " + $num }
            $span1 = $doc.CreateElement("span", "")
            $span1.InnerText = "$num "
            $span1.SetAttribute("class", "index")
            $pre.AppendChild($span1) | Out-Null
            
            $span2 = $doc.CreateElement("span", "")
            $span2.InnerText = $_
            $pre.AppendChild($span2) | Out-Null
            
            $sdiv.AppendChild($pre) | Out-Null
            $pre.SetAttribute("class", "odd")
            if (0 -eq ($line % 2)) { $pre.SetAttribute("class", "even") }
            
            $localhere = @($local | % { $_.Node } | ? { $_.LineNumber -eq $line })
            if ($localhere.Length -gt 0) { 
                $span2.SetAttribute("class", "violation")
                $pdiv = $doc.CreateElement("div", "")
                Insert-Newline $sdiv
                $sdiv.AppendChild($pdiv) | Out-Null
                $ul = $doc.CreateElement("ul", "")
                $pdiv.AppendChild($ul) | Out-Null
                $localhere | % {
                    $li = $doc.CreateElement("li", "")
                    $li.InnerText = $_.InnerText
                    $ul.AppendChild($li) | Out-Null
                    Insert-Newline $ul
                }
            }
            
            Insert-Newline $sdiv
        }
    } # $vfiles
    
    $report.Save($htmlfile)
} # function

$files = @(dir *.StyleCop.xml)
$files | % { Make-HTML $_.FullName }

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.

0 comments: