Skip to content

Commit

Permalink
One more refactoring for SolutionCop.MSBuild
Browse files Browse the repository at this point in the history
  • Loading branch information
Litee committed Nov 18, 2015
1 parent 8676434 commit 6c3a82b
Show file tree
Hide file tree
Showing 18 changed files with 127 additions and 100 deletions.
11 changes: 9 additions & 2 deletions deployment/SolutionCop.MSBuild.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
<description>Tool for static analysis of Visual Studio solutions and projects.</description>
<language>en-US</language>
<tags>VS, VisualStudio, StyleCop, FxCop</tags>
<releaseNotes>https://github.com/Litee/SolutionCop/releases</releaseNotes>
<releaseNotes>
0.6.0-beta1 - First public release"
...
</releaseNotes>
</metadata>
<files>
<file src="..\src\SolutionCop.MSBuild\Bin\Debug\*.dll" target="tools/bin" />
<file src="..\src\SolutionCop.MSBuild\Bin\Release\SolutionCop.MSBuild.dll" target="tools/bin" />
<file src="..\src\SolutionCop.MSBuild\Bin\Release\SolutionCop.Core.dll" target="tools/bin" />
<file src="..\src\SolutionCop.DefaultRules\Bin\Release\SolutionCop.DefaultRules.dll" target="tools/bin" />
<file src="..\src\SolutionCop.DefaultRules\Bin\Release\NuGet.Core.dll" target="tools/bin" />
<file src="..\src\SolutionCop.DefaultRules\Bin\Release\Microsoft.Web.XmlTransform.dll" target="tools/bin" />
<file src="..\src\build\*.Targets" target="build" />
</files>
</package>
13 changes: 7 additions & 6 deletions deployment/SolutionCop.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<language>en-US</language>
<tags>VS, VisualStudio, StyleCop, FxCop</tags>
<releaseNotes>
0.6.0-beta1 - Refactoring for new SolutionCop.MSBuild package
0.5.2 - {BugFix} Now error is properly reported when NuGet binary is referenced, but there is no packages.config file
0.5.1 - {New} Assembly alias added for ReferenceNuGetPackagesOnly rule exceptions
0.5.0 - {New} Possible to specify files in ReferenceNuGetPackagesOnly rule exceptions
Expand All @@ -24,11 +25,11 @@
</releaseNotes>
</metadata>
<files>
<file src="..\src\Bin\Debug\SolutionCop.exe" target="tools/bin" />
<file src="..\src\Bin\Debug\SolutionCop.Core.dll" target="tools/bin" />
<file src="..\src\Bin\Debug\SolutionCop.DefaultRules.dll" target="tools/bin" />
<file src="..\src\Bin\Debug\CommandLine.dll" target="tools/bin" />
<file src="..\src\Bin\Debug\NuGet.Core.dll" target="tools/bin" />
<file src="..\src\Bin\Debug\Microsoft.Web.XmlTransform.dll" target="tools/bin" />
<file src="..\src\SolutionCop.CommandLine\Bin\Release\SolutionCop.exe" target="tools/bin" />
<file src="..\src\SolutionCop.CommandLine\Bin\Release\SolutionCop.Core.dll" target="tools/bin" />
<file src="..\src\SolutionCop.CommandLine\Bin\Release\CommandLine.dll" target="tools/bin" />
<file src="..\src\SolutionCop.DefaultRules\Bin\Release\SolutionCop.DefaultRules.dll" target="tools/bin" />
<file src="..\src\SolutionCop.DefaultRules\Bin\Release\NuGet.Core.dll" target="tools/bin" />
<file src="..\src\SolutionCop.DefaultRules\Bin\Release\Microsoft.Web.XmlTransform.dll" target="tools/bin" />
</files>
</package>
3 changes: 2 additions & 1 deletion deployment/build-and-pack.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
msbuild ..\src\SolutionCop.sln /p:Configuration=Release

nuget pack SolutionCop.nuspec -Version %1
nuget pack SolutionCop.nuspec -Version %1
nuget pack SolutionCop.MSBuild.nuspec -Version %1
59 changes: 45 additions & 14 deletions src/SolutionCop.CommandLine/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

namespace SolutionCop.CommandLine
Expand All @@ -11,13 +12,14 @@ namespace SolutionCop.CommandLine

internal static class Program
{
private static readonly DefaultSolutionCopConsole SolutionCopConsole = new DefaultSolutionCopConsole();

private static void Main(string[] args)
{
var commandLineParameters = new CommandLineParameters();
if (Parser.Default.ParseArguments(args, commandLineParameters))
{
Console.Out.WriteLine("INFO: StyleCop version " + Assembly.GetEntryAssembly().GetName().Version);
SolutionCopConsole.LogInfo("StyleCop version " + Assembly.GetEntryAssembly().GetName().Version);
VerifySolution(commandLineParameters);
}
else
Expand All @@ -28,24 +30,53 @@ private static void Main(string[] args)

private static void VerifySolution(CommandLineParameters commandLineParameters)
{
var solutionInfo = SolutionParser.LoadFromFile(commandLineParameters.PathToSolution);
var solutionInfo = SolutionParser.LoadFromFile(SolutionCopConsole, commandLineParameters.PathToSolution);

if (!solutionInfo.IsParsed)
if (solutionInfo.IsParsed)
{
Console.Out.WriteLine("FATAL: Cannot parse solution file.");
Environment.Exit(-1);
}
var pathToConfigFile = commandLineParameters.PathToConfigFile;
if (string.IsNullOrEmpty(pathToConfigFile))
{
pathToConfigFile = Path.Combine(Path.GetDirectoryName(commandLineParameters.PathToSolution), "SolutionCop.xml");
SolutionCopConsole.LogInfo("Custom path to config file is not specified, using default one: {0}", pathToConfigFile);
}


var pathToConfigFile = commandLineParameters.PathToConfigFile;
if (string.IsNullOrEmpty(pathToConfigFile))
var verificationResult = new ProjectsVerifier(SolutionCopConsole).VerifyProjects(pathToConfigFile, solutionInfo.ProjectFilePaths.ToArray(), (errors, validationResults) =>
{
if (errors.Any())
{
SolutionCopConsole.LogError("***** Full list of errors: *****");
errors.ForEach(x => SolutionCopConsole.LogError(x));
if (commandLineParameters.BuildServerType == BuildServer.TeamCity)
{
// adding empty line for a better formatting in TC output
var extendedErrorsInfo = Enumerable.Repeat(string.Empty, 1).Concat(errors.Select((x, idx) => string.Format("ERROR ({0}/{1}): {2}", idx + 1, errors.Count, x))).Concat(Enumerable.Repeat(String.Empty, 1)).Concat(validationResults.Select(x => String.Format("INFO: Rule {0} is {1}", x.RuleId, x.IsEnabled ? "enabled" : "disabled")));
Console.Out.WriteLine("##teamcity[testFailed name='SolutionCop' message='FAILED - {0}' details='{1}']", EscapeForTeamCity(Path.GetFileName(pathToConfigFile)), string.Join("|r|n", extendedErrorsInfo.Select(EscapeForTeamCity)));
Console.Out.WriteLine("##teamcity[buildStatus text='FAILED - {0}']", EscapeForTeamCity(Path.GetFileName(pathToConfigFile)));
}
}
else
{
SolutionCopConsole.LogInfo("No errors found!");
if (commandLineParameters.BuildServerType == BuildServer.TeamCity)
{
Console.Out.WriteLine("##teamcity[buildStatus status='SUCCESS' text='PASSED - {0}']", EscapeForTeamCity(Path.GetFileName(pathToConfigFile)));
}
}
});
Environment.Exit(verificationResult == VerificationResult.ErrorsFound ? -1 : 0);
}
else
{
pathToConfigFile = Path.Combine(Path.GetDirectoryName(commandLineParameters.PathToSolution), "SolutionCop.xml");
Console.Out.WriteLine("INFO: Custom path to config file is not specified, using default one: {0}", pathToConfigFile);
SolutionCopConsole.LogError("Cannot parse solution file.");
Environment.Exit(-1);
}
}


var errors = new ProjectsVerifier(new DefaultAnalysisLogger()).VerifyProjects(pathToConfigFile, solutionInfo.ProjectFilePaths.ToArray(), commandLineParameters.BuildServerType);
Environment.Exit(errors.Any() ? -1 : 0);
private static string EscapeForTeamCity(string originalString)
{
return originalString.Replace("|", "||").Replace("'", "|'").Replace("\r", "|r").Replace("\n", "|n").Replace("]", "|]");
}
}
}
2 changes: 1 addition & 1 deletion src/SolutionCop.CommandLine/SolutionCop.CommandLine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\bin\Debug\</OutputPath>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
Expand Down
2 changes: 1 addition & 1 deletion src/SolutionCop.Core.Tests/ConfigurationFileParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public void Should_fail_for_malformed_xml()

private ConfigurationFileParser CreateInstance(Action<string, byte[]> saveConfigFileAction)
{
return new ConfigurationFileParser(saveConfigFileAction, new DefaultAnalysisLogger());
return new ConfigurationFileParser(saveConfigFileAction, new DefaultSolutionCopConsole());
}

private class DummyRule : IProjectRule
Expand Down
6 changes: 3 additions & 3 deletions src/SolutionCop.Core.Tests/SolutionParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class SolutionParserTests
[Fact]
public void Should_parse_valid_solution_file()
{
var solutionInfo = SolutionParser.LoadFromFile(new FileInfo(@"..\..\Data\ValidSolutionVS2013.sln").FullName);
var solutionInfo = SolutionParser.LoadFromFile(new DefaultSolutionCopConsole(), new FileInfo(@"..\..\Data\ValidSolutionVS2013.sln").FullName);
Assert.True(solutionInfo.IsParsed);
Assert.NotEmpty(solutionInfo.ProjectFilePaths);
Approvals.VerifyAll(solutionInfo.ProjectFilePaths.Select(x => Path.GetFileName(x)), "PathsToProjects");
Expand All @@ -22,15 +22,15 @@ public void Should_parse_valid_solution_file()
[Fact]
public void Should_parse_empty_solution_file()
{
var solutionInfo = SolutionParser.LoadFromFile(new FileInfo(@"..\..\Data\EmptySolutionVS2013.sln").FullName);
var solutionInfo = SolutionParser.LoadFromFile(new DefaultSolutionCopConsole(), new FileInfo(@"..\..\Data\EmptySolutionVS2013.sln").FullName);
Assert.True(solutionInfo.IsParsed);
Assert.Empty(solutionInfo.ProjectFilePaths);
}

[Fact]
public void Should_fail_for_non_existing_file()
{
var solutionInfo = SolutionParser.LoadFromFile(@"C:\NonExistingSolution.sln");
var solutionInfo = SolutionParser.LoadFromFile(new DefaultSolutionCopConsole(), @"C:\NonExistingSolution.sln");
Assert.False(solutionInfo.IsParsed);
Assert.Empty(solutionInfo.ProjectFilePaths);
}
Expand Down
16 changes: 8 additions & 8 deletions src/SolutionCop.Core/ConfigurationFileParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
public class ConfigurationFileParser
{
private readonly Action<string, byte[]> _saveConfigFileAction;
private readonly IAnalysisLogger _logger;
private readonly ISolutionCopConsole _logger;

public ConfigurationFileParser(IAnalysisLogger logger) : this(File.WriteAllBytes, logger)
public ConfigurationFileParser(ISolutionCopConsole logger) : this(File.WriteAllBytes, logger)
{
}

// Constructor is used for testing
internal ConfigurationFileParser(Action<string, byte[]> saveConfigFileAction, IAnalysisLogger logger)
internal ConfigurationFileParser(Action<string, byte[]> saveConfigFileAction, ISolutionCopConsole logger)
{
_saveConfigFileAction = saveConfigFileAction;
_logger = logger;
Expand All @@ -28,12 +28,12 @@ public Dictionary<string, XElement> ParseConfigFile(string pathToConfigFile, IEn
{
if (File.Exists(pathToConfigFile))
{
_logger.LogInfo("INFO: Existing config file found: {0}", pathToConfigFile);
_logger.LogInfo("Existing config file found: {0}", pathToConfigFile);
return ParseConfigString(pathToConfigFile, File.ReadAllText(pathToConfigFile), rules, errors);
}
else
{
_logger.LogWarning("WARN: Config file does not exist. Creating a new one {0}", pathToConfigFile);
_logger.LogWarning("Config file does not exist. Creating a new one {0}", pathToConfigFile);
return ParseConfigString(pathToConfigFile, "<Rules></Rules>", rules, errors);
}
}
Expand Down Expand Up @@ -61,7 +61,7 @@ public Dictionary<string, XElement> ParseConfigString(string pathToConfigFile, s

// Adding default section into original DOM for saving
xmlRules.Add(rule.DefaultConfig);
_logger.LogWarning("WARN: No config specified for rule {0} - adding default one", rule.Id);
_logger.LogWarning("No config specified for rule {0} - adding default one", rule.Id);
saveConfigFileOnExit = true;
}
else
Expand All @@ -72,11 +72,11 @@ public Dictionary<string, XElement> ParseConfigString(string pathToConfigFile, s
var unknownSectionNames = xmlRules.Elements().Select(x => x.Name.LocalName).Except(rules.Select(x => x.Id));
foreach (var unknownSectionName in unknownSectionNames)
{
_logger.LogWarning("WARN: Unknown configuration section {0}", unknownSectionName);
_logger.LogWarning("Unknown configuration section {0}", unknownSectionName);
}
if (saveConfigFileOnExit)
{
_logger.LogDebug("DEBUG: Config file was updated. Saving...");
_logger.LogDebug("Config file was updated. Saving...");
var settings = new XmlWriterSettings
{
Encoding = Encoding.UTF8,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,26 @@

namespace SolutionCop.Core
{
public class DefaultAnalysisLogger : IAnalysisLogger
public class DefaultSolutionCopConsole : ISolutionCopConsole
{
public void LogDebug(string message, params object[] args)
{
Console.WriteLine(message, args);
Console.WriteLine("DEBUG: " + message, args);
}

public void LogInfo(string message, params object[] args)
{
Console.WriteLine(message, args);
Console.WriteLine("INFO: " + message, args);
}

public void LogWarning(string message, params object[] args)
{
Console.WriteLine(message);
Console.WriteLine("WARNING: " + message, args);
}

public void LogError(string message, params object[] args)
{
Console.WriteLine(message);
Console.WriteLine("ERROR: " + message, args);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
namespace SolutionCop.Core
{
public interface IAnalysisLogger
public interface ISolutionCopConsole
{
void LogDebug(string message, params object[] args);

Expand Down
44 changes: 13 additions & 31 deletions src/SolutionCop.Core/ProjectsVerifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ namespace SolutionCop.Core
{
public class ProjectsVerifier
{
private readonly IAnalysisLogger _logger;
private readonly ISolutionCopConsole _logger;
private readonly RulesDirectoryCatalog _rulesDirectoryCatalog;
private readonly ConfigurationFileParser _configurationFileParser;

public ProjectsVerifier(IAnalysisLogger logger)
public ProjectsVerifier(ISolutionCopConsole logger)
{
_logger = logger;
_configurationFileParser = new ConfigurationFileParser(logger);
_rulesDirectoryCatalog = new RulesDirectoryCatalog(logger);
}

public List<string> VerifyProjects(string pathToConfigFile, string[] projectFilePaths, BuildServer buildServerType = BuildServer.None)
public VerificationResult VerifyProjects(string pathToConfigFile, string[] projectFilePaths, Action<List<string>, List<ValidationResult>> dumpResultsAction)
{
var errors = new List<string>();
var validationResults = new List<ValidationResult>();
Expand All @@ -27,7 +27,7 @@ public List<string> VerifyProjects(string pathToConfigFile, string[] projectFile

var ruleConfigsMap = _configurationFileParser.ParseConfigFile(pathToConfigFile, rules, errors);

_logger.LogInfo("INFO: Starting analysis...");
_logger.LogInfo("Starting analysis...");
foreach (var rule in rules)
{
var xmlRuleConfigs = ruleConfigsMap[rule.Id];
Expand All @@ -36,39 +36,21 @@ public List<string> VerifyProjects(string pathToConfigFile, string[] projectFile
errors.Add(String.Format("Configuration section is not found for rule {0}", rule.Id));
continue;
}
_logger.LogInfo("INFO: Analyzing projects using rule {0}", rule.Id);
_logger.LogInfo("Analyzing projects using rule {0}", rule.Id);
var validationResult = rule.ValidateAllProjects(xmlRuleConfigs, projectFilePaths);
errors.AddRange(validationResult.Errors);
validationResults.Add(validationResult);
}
_logger.LogInfo("INFO: Analysis finished!");
_logger.LogInfo("Analysis finished!");

if (errors.Any())
{
_logger.LogError("ERROR: ***** Full list of errors: *****");
errors.ForEach(x => _logger.LogError("ERROR: {0}", x));
if (buildServerType == BuildServer.TeamCity)
{
// adding empty line for a better formatting in TC output
var extendedErrorsInfo = Enumerable.Repeat(String.Empty, 1).Concat(errors.Select((x, idx) => String.Format("ERROR ({0}/{1}): {2}", idx + 1, errors.Count, x))).Concat(Enumerable.Repeat(String.Empty, 1)).Concat(validationResults.Select(x => String.Format("INFO: Rule {0} is {1}", x.RuleId, x.IsEnabled ? "enabled" : "disabled")));
Console.Out.WriteLine("##teamcity[testFailed name='SolutionCop' message='FAILED - {0}' details='{1}']", EscapeForTeamCity(Path.GetFileName(pathToConfigFile)), string.Join("|r|n", extendedErrorsInfo.Select(EscapeForTeamCity)));
Console.Out.WriteLine("##teamcity[buildStatus text='FAILED - {0}']", EscapeForTeamCity(Path.GetFileName(pathToConfigFile)));
}
}
else
{
_logger.LogInfo("INFO: No errors found!");
if (buildServerType == BuildServer.TeamCity)
{
Console.Out.WriteLine("##teamcity[buildStatus status='SUCCESS' text='PASSED - {0}']", EscapeForTeamCity(Path.GetFileName(pathToConfigFile)));
}
}
return errors;
dumpResultsAction(errors, validationResults);
return errors.Any() ? VerificationResult.ErrorsFound : VerificationResult.NoErrors;
}
}

private static string EscapeForTeamCity(string originalString)
{
return originalString.Replace("|", "||").Replace("'", "|'").Replace("\r", "|r").Replace("\n", "|n").Replace("]", "|]");
}
public enum VerificationResult
{
NoErrors,
ErrorsFound
}
}
Loading

0 comments on commit 6c3a82b

Please sign in to comment.