Skip to content

Commit

Permalink
Add FxCop parser
Browse files Browse the repository at this point in the history
Fix #6
  • Loading branch information
Jérémie Bertrand committed Sep 5, 2016
1 parent bba2afa commit 2ff0c96
Show file tree
Hide file tree
Showing 8 changed files with 608 additions and 14 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ It is possible to process several reports at the same time: `NVika parsereport r
### Supported
- [InspectCode](https://chocolatey.org/packages/resharper-clt): example of usage `inspectcode /o="inspectcodereport.xml" "Vika.sln"`
- Analyzers producing [SARIF](http://sarifweb.azurewebsites.net) format, like Roslyn analyzers: for those you need to add an `ErrorLog` node in your `csproj` containing the path of the report; See [NVika.csproj](https://github.com/laedit/vika/blob/master/src/NVika/NVika.csproj)
- [FxCop](https://msdn.microsoft.com/en-us/library/bb429476(v=vs.80).aspx): example of usage `fxcopcmd /file:NVika.dll /out:FxCopResults.xml`. Or activate Code Analysis in the corresponding tab of your project properties in Visual Studio.

### To come
- [FxCop](https://github.com/laedit/vika/issues/6)
Expand Down
26 changes: 12 additions & 14 deletions build.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,20 @@ Target "BuildApp" (fun _ ->
Target "InspectCodeAnalysis" (fun _ ->
"resharper-clt.portable" |> Choco.Install id

if directExec(fun info ->
directExec(fun info ->
info.FileName <- "inspectcode"
info.Arguments <- "/o=\"" + artifactsDir + "inspectcodereport.xml\" /project=\"NVika\" \"src\Vika.sln\"" )
then
artifactsDir + "inspectcodereport.xml"
|> NVika.ParseReport (fun p -> { p with Debug = true; IncludeSource = true; ToolPath = buildDir @@ "NVika.exe" })

else traceError "Execution of inspectcode have failed, NVika can't be executed."
info.Arguments <- "/o=\"" + artifactsDir + "inspectcodereport.xml\" /project=\"NVika\" \"src\Vika.sln\"" ) |> ignore
)

Target "RoslynAnalysis" (fun _ ->
let roslynReportPath = buildDir + "static-analysis.sarif.json"
if fileExists roslynReportPath then
roslynReportPath |> NVika.ParseReport (fun p -> { p with Debug = true; IncludeSource = true; ToolPath = buildDir @@ "NVika.exe" })
else
traceError "file 'static-analysis.sarif.json' not found"
Target "LaunchNVika" (fun _ ->
let reportsPath =
[
artifactsDir + "inspectcodereport.xml";
buildDir + "static-analysis.sarif.json";
buildDir + "NVika.exe.CodeAnalysisLog.xml";
]
let existingReportsPath = reportsPath |> Seq.filter fileExists
existingReportsPath |> NVika.ParseReports (fun p -> { p with Debug = true; IncludeSource = true; ToolPath = buildDir @@ "NVika.exe" })
)

Target "BuildReleaseNotes" (fun _ ->
Expand Down Expand Up @@ -181,7 +179,7 @@ Target "All" DoNothing
==> "RestorePackages"
==> "BuildApp"
=?> ("InspectCodeAnalysis", Choco.IsAvailable)
==> "RoslynAnalysis"
==> "LaunchNVika"
==> "BuildReleaseNotes"
==> "BuildTest"
==> "Test"
Expand Down
370 changes: 370 additions & 0 deletions src/NVika.Tests/Data/CodeAnalysisLog.xml

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/NVika.Tests/NVika.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Parsers\FxCopParserTest.cs" />
<Compile Include="TestUtilities.cs" />
<EmbeddedResource Include="Data\AppVeyor.txt" />
<Compile Include="ParseReportCommandTest.cs" />
Expand Down Expand Up @@ -129,6 +130,9 @@
<ItemGroup>
<EmbeddedResource Include="Data\inspectcodereport_2016.2.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Data\CodeAnalysisLog.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Expand Down
134 changes: 134 additions & 0 deletions src/NVika.Tests/Parsers/FxCopParserTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
using NVika.Parsers;
using System;
using System.Collections.Generic;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace NVika.Tests.Parsers
{
public class FxCopParserTest
{
[Fact]
public void Name()
{
// arrange
var parser = new FxCopParser();
parser.FileSystem = new MockFileSystem();

// act
var name = parser.Name;

// assert
Assert.Equal("FxCop", name);
}

[Theory]
[InlineData("CodeAnalysisLog.xml", true)]
[InlineData("emptyreport.xml", true)]
[InlineData("onlymessages.xml", true)]
[InlineData("onlyrules.xml", true)]
[InlineData("onlyissues.json", false)]
public void CanParse(string reportPath, bool expectedResult)
{
// arrange
var parser = new FxCopParser();
parser.FileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ "CodeAnalysisLog.xml", new MockFileData(TestUtilities.GetEmbeddedResourceContent("CodeAnalysisLog.xml")) },
{ "emptyreport.xml", new MockFileData("<FxCopReport Version=\"10.0\"></FxCopReport>") },
{ "onlymessages.xml", new MockFileData("<FxCopReport Version=\"10.0\"><Messages><Message /></Messages></FxCopReport>") },
{ "onlyrules.xml", new MockFileData("<FxCopReport Version=\"10.0\"><Rules><Rule /></Rules></FxCopReport>") },
});

// act
var result = parser.CanParse(reportPath);

// assert
Assert.Equal(expectedResult, result);
}

[Fact]
public void Parse()
{
// arrange
var fileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ "CodeAnalysisLog.xml", new MockFileData(TestUtilities.GetEmbeddedResourceContent("CodeAnalysisLog.xml")) },
});
var parser = new FxCopParser();
parser.FileSystem = fileSystem;

// act
var result = parser.Parse("CodeAnalysisLog.xml").ToList();

// assert
Assert.Equal(24, result.Count);

AssertIssue(result[0],
"Microsoft.Naming",
"Identifiers should be spelled correctly",
"http://msdn.microsoft.com/library/bb264492.aspx",
"Correct the spelling of 'Vika' in namespace name 'NVika.Abstractions'.",
"CA1704",
IssueSeverity.Warning);

AssertIssue(result[4],
"Microsoft.Design",
"Mark assemblies with CLSCompliantAttribute",
"http://msdn.microsoft.com/library/ms182156.aspx",
"Mark 'NVika.exe' with CLSCompliant(true) because it exposes externally visible types.",
"CA1014",
IssueSeverity.Error);

AssertIssue(result[10],
"Microsoft.Globalization",
"Specify IFormatProvider",
"http://msdn.microsoft.com/library/ms182190.aspx",
"Because the behavior of 'string.Format(string, object)' could vary based on the current user's locale settings, replace this call in 'Program.Run(string[])' with a call to 'string.Format(IFormatProvider, string, params object[])'. If the result of 'string.Format(IFormatProvider, string, params object[])' will be displayed to the user, specify 'CultureInfo.CurrentCulture' as the 'IFormatProvider' parameter. Otherwise, if the result will be stored and accessed by software, such as when it is persisted to disk or to a database, specify 'CultureInfo.InvariantCulture'.",
"CA1305",
IssueSeverity.Error);

AssertIssue(result[16],
"Microsoft.Performance",
"Avoid uncalled private code",
"http://msdn.microsoft.com/library/ms182264.aspx",
"'AppVeyor.CompilationMessage.Line.get()' appears to have no upstream public or protected callers.",
"CA1811",
IssueSeverity.Warning);

AssertIssue(result[19],
"Microsoft.Design",
"Implement standard exception constructors",
"http://msdn.microsoft.com/library/ms182151.aspx",
"Add the following constructor to 'LoadingReportException': private LoadingReportException(SerializationInfo, StreamingContext).",
"CA1032",
IssueSeverity.Error);

AssertIssue(result[23],
"Microsoft.Globalization",
"Specify IFormatProvider",
"http://msdn.microsoft.com/library/ms182190.aspx",
"Because the behavior of 'int.Parse(string)' could vary based on the current user's locale settings, replace this call in 'InspectCodeParser.GetOffset(XAttribute, string, uint?)' with a call to 'int.Parse(string, IFormatProvider)'. If the result of 'int.Parse(string, IFormatProvider)' will be based on input from the user, specify 'CultureInfo.CurrentCulture' as the 'IFormatProvider' parameter. Otherwise, if the result will based on input stored and accessed by software, such as when it is loaded from disk or from a database, specify 'CultureInfo.InvariantCulture'.",
"CA1305",
IssueSeverity.Error);
}

private void AssertIssue(Issue result, string expectedCategory, string expectedDescription, string expectedUri, string expectedMessage, string expectedName, IssueSeverity expectedSeverity)
{
Assert.Equal(expectedCategory, result.Category);
Assert.Equal(expectedDescription, result.Description);
Assert.Null(result.FilePath);
Assert.Equal(expectedUri, result.HelpUri.AbsoluteUri);
Assert.Null(result.Line);
Assert.Equal(expectedMessage, result.Message);
Assert.Equal(expectedName, result.Name);
Assert.Null(result.Offset);
Assert.Null(result.Project);
Assert.Equal(expectedSeverity, result.Severity);
Assert.Equal("FxCop", result.Source);
}
}
}
1 change: 1 addition & 0 deletions src/NVika/NVika.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
<Compile Include="Exceptions\LoadingReportException.cs" />
<Compile Include="Exceptions\NVikaException.cs" />
<Compile Include="ExitCodes.cs" />
<Compile Include="Parsers\FxCopParser.cs" />
<Compile Include="Parsers\ReportParserBase.cs" />
<Compile Include="Parsers\SarifParser.cs" />
<Compile Include="Properties\GlobalSuppressions.cs" />
Expand Down
85 changes: 85 additions & 0 deletions src/NVika/Parsers/FxCopParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;

namespace NVika.Parsers
{
internal sealed class FxCopParser : ReportParserBase
{
private readonly Dictionary<string, XElement> _rules = new Dictionary<string, XElement>();

public override string Name
{
get
{
return "FxCop";
}
}

public FxCopParser()
: base(new[] { ".xml" }, '<')
{

}

protected override bool CanParse(StreamReader streamReader)
{
// Avoid Xml exception caused by the BOM
using (var xmlReader = new XmlTextReader(streamReader.BaseStream))
{
var report = XDocument.Load(xmlReader);

return report.Root.Name == "FxCopReport";
}
}

public override IEnumerable<Issue> Parse(string reportPath)
{
var report = XDocument.Load(FileSystem.File.OpenRead(reportPath));

var rules = report.Descendants("Rule");

foreach (var message in report.Descendants("Message"))
{
var rule = GetRule(rules, message.Attribute("CheckId").Value);
var issue = message.Element("Issue");

yield return new Issue
{
Category = rule.Attribute("Category").Value,
Description = rule.Element("Name").Value,
HelpUri = new Uri(rule.Element("Url").Value),
Message = issue.Value,
Name = message.Attribute("CheckId").Value,
Severity = GetSeverity(issue.Attribute("Level")),
Source = Name,
};
}
}

private static IssueSeverity GetSeverity(XAttribute level)
{
switch (level.Value)
{
case "Error":
case "CriticalError": return IssueSeverity.Error;

case "Warning":
case "CriticalWarning":
default: return IssueSeverity.Warning;
}
}

private XElement GetRule(IEnumerable<XElement> rules, string ruleId)
{
if (!_rules.ContainsKey(ruleId))
{
_rules.Add(ruleId, rules.First(it => it.Attribute("CheckId").Value == ruleId));
}
return _rules[ruleId];
}
}
}
1 change: 1 addition & 0 deletions src/NVika/Properties/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CC0021:Use nameof", Justification = "Not from the type 'Issue'", Scope = "member", Target = "~M:NVika.Parsers.InspectCodeParser.CanParse(System.IO.StreamReader)~System.Boolean")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CC0021:Use nameof", Justification = "Not from the type 'Issue'", Scope = "member", Target = "~M:NVika.Parsers.InspectCodeParser.Parse(System.String)~System.Collections.Generic.IEnumerable{NVika.Parsers.Issue}")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CC0021:Use nameof", Justification = "Not from the property 'Name'", Scope = "member", Target = "~M:NVika.Parsers.FxCopParser.Parse(System.String)~System.Collections.Generic.IEnumerable{NVika.Parsers.Issue}")]

0 comments on commit 2ff0c96

Please sign in to comment.