65.9K
CodeProject 正在变化。 阅读更多。
Home

在 TortoiseSVN 提交时检查 StyleCop 规则

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (3投票s)

2014 年 3 月 24 日

CPOL

4分钟阅读

viewsIcon

23501

downloadIcon

106

此钩子在代码提交到存储库之前或之后使用 StyleCop 进行验证,以确保其符合验证规则。

引言

Bob Martin 叔叔 在他的经典著作《代码整洁之道:敏捷软件开发修炼之道》中提倡的理念之一是,你应该“让代码比你接手时更整洁。”

在我们的环境中,代码库已经维护了 20 多年。为了确保代码的一致性并突出潜在的编码违规,我们使用 StyleCop。开发人员负责在他们的 IDE 中检查代码,并且在自动化构建环境中收集团队和代码指标。代码库是稳定的,在很多情况下,您希望将功能性更改与重构分开。如果您在进行重大的功能更改的同时重新格式化代码,那么就很难回答这个问题:“有什么变化?”

我们当前的 StyleCop 检查是处于被动状态,这意味着您依赖开发人员来执行它,或者查看构建统计信息。对于大型代码库,您希望在修改代码时逐步解决问题。基于这种情况,理想的解决方案是在提交时向开发人员显示所有“代码异味”。如果有快速改进的机会,开发人员可以在代码的功能在他/她的脑海中仍然清晰时进行处理。利用 TortoiseSVN Hook,您可以配置 Stylecop 配置来评估您的代码,并在提交时显示所有违规行为。

这可能会被视为对开发人员的“老大哥”式强制。钩子的可配置性为开发人员和您的特定环境提供了灵活性。这消除了在不遵守标准时“推卸责任”的借口。

  • TortoiseSVN 中的钩子函数允许触发 StyleCop 分析的时间。它允许在提交生命周期的 7 个不同位置触发该功能。
  • StyleCop 的全部灵活性和可配置性可用于自定义体验。
  • 你有源代码,还需要什么?

构建代码

此钩子的基础结合了 C# 中的 TortoiseSVN 预提交钩子 - 拯救自己的一些麻烦!从代码运行 StyleCop 来处理每个提交的文件。

要编译,请创建一个控制台项目,并添加对 StyleCop DLL 的引用。务必包含 Rules DLL,因为没有违规行为将无法进行任何规则检查。在我的系统上,它们安装在C:\Program Files (x86)\StyleCop 4.7

StyleCop References

用以下代码替换Program.c

using StyleCop;
using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace TortoiseSVNStyleCop
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            int foundViolatons = 0;
            string[] filePaths = File.ReadAllLines(args[0]);
            string projectPath = GetRootPath(filePaths);
            string settingsPath = Path.Combine
            (System.Reflection.Assembly.GetExecutingAssembly().Location, @"Settings.StyleCop");
            if (File.Exists(settingsPath))
            {
                settingsPath = null;
            }
            Console.Error.WriteLine("DEBUG: {0}", settingsPath);
            StyleCopConsole styleCopConsole = new StyleCopConsole(settingsPath, false, null, null, true);
            Configuration configuration = new Configuration(null);
            CodeProject project = new CodeProject(0, projectPath, configuration);
            foreach (string file in filePaths)
            {
                var loaded = styleCopConsole.Core.Environment.AddSourceCode(project, file, null);
            }
            List<violation> violations = new List<violation>();
            styleCopConsole.ViolationEncountered += ((sender, arguments) => violations.Add(arguments.Violation));
            List<string> output = new List<string>();
            styleCopConsole.OutputGenerated += ((sender, arguments) => output.Add(arguments.Output));
            styleCopConsole.Start(new[] { project }, true);
            foreach (string file in filePaths)
            {
                List<violation> fileViolations = violations.FindAll(viol => viol.SourceCode.Path == file);
                if (fileViolations.Count > 0)
                {
                    foundViolatons = 1;
                    Console.Error.WriteLine("{0} - {1} violations.", fileViolations[0].SourceCode.Name, fileViolations.Count);
                    foreach (Violation violation in fileViolations)
                    {
                        Console.Error.WriteLine("      {0}: Line {1}-{2}", violation.Rule.CheckId, violation.Line, violation.Message);
                    }
                }
            }
            Environment.Exit(foundViolatons);
        }
        private static string GetRootPath(string[] filePaths)
        {
            if (filePaths.Length > 0)
            {
                string[] testAgainst = filePaths[0].Split('/');
                int noOfLevels = testAgainst.Length;
                foreach (string filePath in filePaths)
                {
                    string[] current = filePath.Split('/');
                    int level;
                    for (level = 0; level <= Math.Min(noOfLevels, current.Length) - 1; level++)
                    {
                        if (testAgainst[level] != current[level])
                        {
                            break;
                        }
                    }
                    noOfLevels = Math.Min(noOfLevels, level);
                }
                return (testAgainst.Take(noOfLevels).Aggregate((m, n) => m + "/" + n));
            }
            return string.Empty;
        }
    }
}

注册钩子

在部署了 SVN 的目录中,右键单击并打开“设置”选项卡。在“钩子脚本”条目下,添加一个新的钩子。

StyleCop References

工作副本路径是此钩子生效的根目录。如果钩子被调用时传入此目录,那将是很好的。这将允许您从该目录加载 StyleCop 设置。

根据您想何时或如何响应,您可以将其注册为预提交钩子。这将使您有机会停止提交,直到没有违规为止。如果您的模式是提交更改然后重构,您可能选择将其注册为更新后钩子。

StyleCop References

如果您仍在积极开发中,只需将其指向您的bin\Debug 目录。您可以将 StyleCop.Settings 添加到您的项目中,但请确保“复制到输出目录”属性设置为“始终复制”,以便添加到您的部署中。

调用钩子

如果配置正确且存在 StyleCop 违规,您应该会看到类似以下内容:

StyleCop References

您将在截图上看到一个 DEBUG 条目。在开发过程中,您可能想查看一些内部变量。只需写入 Console.Error 即可。

注释

这是我第一次使用 Visual Studio 2013(从 2010 迁移过来)并需要考虑的一些事项。

  • 创建项目时,您必须将目标框架(项目属性)设置为 .NET Framework 4 - 它默认设置为 .NET Framework 4 Client
  • 我最初链接 StyleCop 的想法是使用 NuGet 将其添加到我的项目中。这将创建编译代码的引用,但运行 StyleCop 将不会产生任何违规。您需要链接规则引擎 => StyleCop.CSharp.Rules ,它随正常的 StyleCop 部署一起提供。
  • 在 Stack Overflow 上,有一个 LINQ 方法 来编写 GetRootPath(),我本可以使用它。即使那样也可能更有效。我觉得它难以阅读,更喜欢逻辑更易读的老式方法。
  • 似乎 StyleCop 会对传递给它的 projectPath 进行某种枚举和分析。如果当前项目目录中有大量文件,我不得不使用任务管理器终止进程。它可能已经完成,但它占用了内存,这就是 GetRootPath() 函数的主要原因。
  • 此代码是为了满足我们的公司需求而编写的。它实现了我的预期功能,并在几个小时内完成。目前,它似乎适用于我的个人用途,但需要更多的工业应用来解决一些问题。请查看 GitHub 仓库 以获取最新版本。

历史

  • 2014 年 3 月 21 日:初始版本
© . All rights reserved.