这个问题不只是询问硬编码字符串,还有魔术数字等。
有没有办法找到所有硬编码值,即字符串,幻数以及VS中的C#项目/解决方案中没有?
提示这个问题的是我正在查看的项目,我刚刚发现174次字符串值被硬编码重复!
答案 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...
}