c# - 如何找到 C# 项目中的所有硬编码值(解决方案)?

标签 c# visual-studio roslyn

这个问题不只问硬编码字符串,还问魔数(Magic Number)等。

有没有办法找到所有硬编码值,即字符串、魔数(Magic Number)以及 VS 中的 C# 项目/解决方案中没有的东西?

提出这个问题的是我正在查看的一个项目,我刚刚发现一个字符串值被硬编码重复了 174 次!

最佳答案

你可以做的是编程Roslyn ,镇上(不是这样)的新酷 child 。它允许您非常轻松地解析 C#(或 VB.NET)项目。然后您可以访问检测到的节点并检查您真正想要检查的内容。检测机器的魔法文字并不总是像人类看起来那么容易。例如,1 真的是一个神奇的数字吗?我个人认为不是,但 2 更值得怀疑......

无论如何,这是一个小示例,我相信它可以完成大部分工作,但它可以/应该改进,也许可以定制您的确切业务需求或规则(这非常有趣)。

注意 Roslyn 也可以直接在 Visual Studio 上下文中使用,因此您可以将此示例转换为所谓的诊断(Visual Studio 的扩展),它可以帮助您直接在 IDE 中运行。有这方面的样本:Samples and Walkthroughs

class Program
{
    static void Main(string[] args)
    {
        var text = @" 
public class MyClass 
{ 
public void MyMethod() 
{ 
    const int i = 0; // this is ok
    decimal d = 11; // this is not ok
    string s = ""magic"";
    if (i == 29) // another magic
    {
    }
    else if (s != ""again another magic"")
    {
    }
}
}";
        ScanHardcodedFromText("test.cs", text, (n, s) =>
        {
            Console.WriteLine(" " + n.SyntaxTree.GetLineSpan(n.FullSpan) + ": " + s);
        }).Wait();
    }

    public static async Task ScanHardcodedFromText(string documentName, string text, Action<SyntaxNodeOrToken, string> scannedFunction)
    {
        if (text == null)
            throw new ArgumentNullException("text");

        AdhocWorkspace ws = new AdhocWorkspace();
        var project = ws.AddProject(documentName + "Project", LanguageNames.CSharp);
        ws.AddDocument(project.Id, documentName, SourceText.From(text));
        await ScanHardcoded(ws, scannedFunction);
    }

    public static async Task ScanHardcodedFromSolution(string solutionFilePath, Action<SyntaxNodeOrToken, string> scannedFunction)
    {
        if (solutionFilePath == null)
            throw new ArgumentNullException("solutionFilePath");

        var ws = MSBuildWorkspace.Create();
        await ws.OpenSolutionAsync(solutionFilePath);
        await ScanHardcoded(ws, scannedFunction);
    }

    public static async Task ScanHardcodedFromProject(string solutionFilePath, Action<SyntaxNodeOrToken, string> scannedFunction)
    {
        if (solutionFilePath == null)
            throw new ArgumentNullException("solutionFilePath");

        var ws = MSBuildWorkspace.Create();
        await ws.OpenProjectAsync(solutionFilePath);
        await ScanHardcoded(ws, scannedFunction);
    }

    public static async Task ScanHardcoded(Workspace workspace, Action<SyntaxNodeOrToken, string> scannedFunction)
    {
        if (workspace == null)
            throw new ArgumentNullException("workspace");

        if (scannedFunction == null)
            throw new ArgumentNullException("scannedFunction");

        foreach (var project in workspace.CurrentSolution.Projects)
        {
            foreach (var document in project.Documents)
            {
                var tree = await document.GetSyntaxTreeAsync();
                var root = await tree.GetRootAsync();
                foreach (var n in root.DescendantNodesAndTokens())
                {
                    if (!CanBeMagic(n.Kind()))
                        continue;

                    if (IsWellKnownConstant(n))
                        continue;

                    string suggestion;
                    if (IsMagic(n, out suggestion))
                    {
                        scannedFunction(n, suggestion);
                    }
                }
            }
        }
    }

    public static bool IsMagic(SyntaxNodeOrToken kind, out string suggestion)
    {
        var vdec = kind.Parent.Ancestors().OfType<VariableDeclarationSyntax>().FirstOrDefault();
        if (vdec != null)
        {
            var dec = vdec.Parent as MemberDeclarationSyntax;
            if (dec != null)
            {
                if (!HasConstOrEquivalent(dec))
                {
                    suggestion = "member declaration could be const: " + dec.ToFullString();
                    return true;
                }
            }
            else
            {
                var ldec = vdec.Parent as LocalDeclarationStatementSyntax;
                if (ldec != null)
                {
                    if (!HasConstOrEquivalent(ldec))
                    {
                        suggestion = "local declaration contains at least one non const value: " + ldec.ToFullString();
                        return true;
                    }
                }
            }
        }
        else
        {
            var expr = kind.Parent.Ancestors().OfType<ExpressionSyntax>().FirstOrDefault();
            if (expr != null)
            {
                suggestion = "expression uses a non const value: " + expr.ToFullString();
                return true;
            }
        }

        // TODO: add other cases?

        suggestion = null;
        return false;
    }

    private static bool IsWellKnownConstant(SyntaxNodeOrToken node)
    {
        if (!node.IsToken)
            return false;

        string text = node.AsToken().Text;
        if (text == null)
            return false;

        // note: this is naïve. we also should add 0d, 0f, 0m, etc.
        if (text == "1" || text == "-1" || text == "0")
            return true;

        // ok for '\0' or '\r', etc.
        if (text.Length == 4 && text.StartsWith("'\\") && text.EndsWith("'"))
            return true;

        if (text == "' '")
            return true;

        // TODO add more of these? or make it configurable...

        return false;
    }

    private static bool HasConstOrEquivalent(SyntaxNode node)
    {
        bool hasStatic = false;
        bool hasReadOnly = false;
        foreach (var tok in node.ChildTokens())
        {
            switch (tok.Kind())
            {
                case SyntaxKind.ReadOnlyKeyword:
                    hasReadOnly = true;
                    if (hasStatic)
                        return true;
                    break;

                case SyntaxKind.StaticKeyword:
                    hasStatic = true;
                    if (hasReadOnly)
                        return true;
                    break;

                case SyntaxKind.ConstKeyword:
                    return true;
            }
        }
        return false;
    }

    private static bool CanBeMagic(SyntaxKind kind)
    {
        return kind == SyntaxKind.CharacterLiteralToken ||
            kind == SyntaxKind.NumericLiteralToken ||
            kind == SyntaxKind.StringLiteralToken;
    }
}

如果您运行这个小程序(我还提供了在解决方案或项目中使用它的辅助方法),它将输出:

 test.cs: (6,20)-(6,22): local declaration contains at least one non const value:         decimal d = 11; // this is not ok

 test.cs: (7,19)-(7,26): local declaration contains at least one non const value:         string s = "magic";

 test.cs: (8,17)-(8,19): expression uses a non const value: i == 29
 test.cs: (11,22)-(11,43): expression uses a non const value: s != "again another magic"

关于c# - 如何找到 C# 项目中的所有硬编码值(解决方案)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29533905/

相关文章:

javascript - 按钮淡入淡出 jQuery ASP.net

c# - 扫描证书存储不显示所有证书

c# - 如何使用 Roslyn 的 AdhocWorkspace 保留项目和文档的解决方案?

c# - 在编译之前将代码插入到 C# 方法中

c# - 将类型 `System.Collections.IEnumerator` 转换为 `System.Collections.Generic.IEnumerator`

c# - 具有许多 Web 项目输出到同一目录的 MSBuild 解决方案

ios - Xamarin 调试无法正常工作

c# - 扩展 C# 语法的想法

c# - VS 2010 的键盘映射方案插件

c# - SyntaxFactory 生成带有调用基类构造函数的构造函数