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

This question does not ask only about hard-coded strings, but also about magic numbers, etc.

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

What caused this question is the project I'm looking at, I just found 174 times the string value has been hard-coded!

+6
c # visual-studio roslyn
Apr 09 '15 at 8:50
source share
2 answers

What you can do is Roslyn , a (not so) new cool guy in town. This makes it easy to analyze C # (or VB.NET) projects. You can then visit the discovered nodes and verify that you really want to verify. Finding magic literals for a machine is not always as easy as it seems to humans. For example, is 1 really a magic number? I personally do not think so, but 2 is more suspicious ...

In any case, here is a small sample that does a good part of the work that I consider, but it can / should be improved, perhaps adapting 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 can turn this sample into a so-called diagnostics (extension for Visual Studio), which can help you live directly in the IDE. There are examples 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 small program (I also provided helper methods for using it in a 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" 
+5
Nov 22 '15 at 17:49
source share

I have code that can find magic numbers and hard-coded inconsistent strings. Maybe this 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 containing the file names to ignore will contain values ​​such as -

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

Give the name of your solution in the solution path and run it. This will generate a txt file for you with all hardcoded strings and magic numbers.

Edit:

To compile the project, you need to install the Microsoft.CodeAnalysis NuGet package in the console project:

 Install-Package Microsoft.CodeAnalysis -Pre 

Here is the complete list of links 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... } 
0
May 19 '16 at 11:13
source share



All Articles