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

时间:2015-04-09 08:50:31

标签: c# visual-studio roslyn

这个问题不只是询问硬编码字符串,还有魔术数字等。

有没有办法找到所有硬编码值,即字符串,幻数以及VS中的C#项目/解决方案中没有?

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

2 个答案:

答案 0 :(得分:5)

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

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

注意Roslyn也可以直接在Visual Studio的上下文中使用,因此您可以将此示例转换为可以帮助您直接在IDE中生存的诊断(Visual Studio的扩展)。有以下示例: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"

答案 1 :(得分:0)

我有一些代码可以找到魔术数字和硬编码的非常数字符串。可能对某人有帮助 -

/// <summary>
/// Scans all cs files in the solutions for magic strings and numbers using the Roslyn 
/// compiler and analyzer tools.
/// Based upon a Roslyn code sample.
/// </summary>
class MagicStringAnalyzer
{
    protected static Filter filter;

    static void Main(string[] args)
    {

        string outputPath = @"E:\output.txt";
        string solutionPath = @"E:\Solution.sln";

        filter = new Filter(@"E:\IgnorePatterns.txt");

        if (File.Exists(outputPath))
        {
            OverWriteFile(outputPath);
        }

        analyzeSolution(outputPath, solutionPath);

    }

    protected static void loadFilters()
    {

    }

    private static void OverWriteFile(string path)
    {
        Console.WriteLine("Do you want to overwrite existing output file? (y/n)");

        if (Console.ReadKey().Key == ConsoleKey.Y)
        {
            File.Delete(path);
            Console.WriteLine("");

        }
        else
        {
            Environment.Exit(-1);
        }
    }

    public static void analyzeSolution(string outputPath, string solutionPath)
    {
        Console.WriteLine("Analyzing file...");


        System.IO.StreamWriter writer = new System.IO.StreamWriter(outputPath);

        ScanHardcodedFromSolution(solutionPath, (n, s) =>
        {
            string syntaxLineSpan = n.SyntaxTree.GetLineSpan(n.FullSpan).ToString();

            if (!filter.IsMatch(syntaxLineSpan))
            {
                writer.WriteLine(" " + syntaxLineSpan + ": \r\n" + s + "\r\n\r\n");
            }
        }).Wait();


        writer.Close();
    }

    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;

        if (text == "")
            return true;

        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;
    }
}


public class Filter
{

    protected string[] patterns;

    public Filter(string path)
    {
        loadFilters(path);
    }

    protected void loadFilters(string path)
    {
        patterns = File.ReadAllLines(path);
    }

    public bool IsMatch(string input)
    {
        foreach (string pattern in patterns)
        {
            if(Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase))
            {
                return true;
            }
        }

        return false;
    }
}

包含要忽略的文件名的txt文件将包含类似 -

的值
Constant.cs
Resoures.Designer.cs
Configuration.cs
Reference.cs
Test

在解决方案路径中输入您的解决方案的名称并运行它。这将为您生成包含所有硬编码字符串和幻数的txt文件。

编辑:

要编译项目,您需要在控制台应用程序项目中安装Microsoft.CodeAnalysis NuGet包:

Install-Package Microsoft.CodeAnalysis -Pre

以下是Program.cs

中应该包含的完整参考列表
using System;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using System.Text.RegularExpressions;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Text;

namespace MagicStringAnalyzer
{
   // the rest of the code goes here...
}