How to find all the hardcoded values in a C# project(solution)?

你离开我真会死。 提交于 2019-12-06 00:08:14

问题


This question is not asking about hard coded strings only, but magic numbers etc. as well.

Is there a way to find all the hard coded values i.e. string , magic numbers and what not in C# project/solution in VS?

What prompted this question is a project that I am looking at, I just found 174 times a string value was hardcodely repeated!


回答1:


What you could do is program Roslyn, the (not so) new cool kid in town. It allows you to parse C# (or VB.NET) projects quite easily. Then you can visit the detected nodes and check what you really want to check. Detecting magic literals for a machine is not always as easy as it seems for a human. For example, is 1 really a magic number? I personally consider it's not, but 2 is more suspect...

Anyway, here is a small sample that does a good part of the job I believe, but it could/should be improved, maybe to tailor your exact business needs or rules (which is very interesting).

Note Roslyn can also be used directly in the context of Visual Studio, so you could turn this sample into what's called a diagnostic (an extension to Visual Studio) that can help you directly live from within the IDE. There are samples for this: 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;
    }
}

If you run this little program (I've also provided helper methods to use it on solution or projects), it will output this:

 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"



回答2:


I have some code which can find magic numbers and hard coded non-constant strings. May be that can help someone -

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

Your txt file that contains file names to ignore would contain values like -

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

Give name of your solution in solution path and run this. This will generate txt file for you with all hard coded strings and magic numbers.

Edit:

To compile the project, you'll need to install Microsoft.CodeAnalysis NuGet package into your console app project:

Install-Package Microsoft.CodeAnalysis -Pre

Here is a complete list of references you should have in your 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...
}


来源:https://stackoverflow.com/questions/29533905/how-to-find-all-the-hardcoded-values-in-a-c-sharp-projectsolution

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!