2015-04-09 8 views
6

Questa domanda non riguarda solo le stringhe codificate, ma anche i numeri magici ecc.Come trovare tutti i valori hardcoded in un progetto C# (soluzione)?

C'è un modo per trovare tutti i valori hard coded cioè stringhe, numeri magici e cosa no in C# progetto/soluzione in VS?

Ciò che ha spinto questa domanda è un progetto che sto guardando, ho trovato solo 174 volte che un valore di stringa è stato ripetuto in modo inequivocabile!

+4

Trova tutto con 'Ctrl + Shift + f' con' RegEx' – VMAtm

+0

@Arjang: utilizzare ReSharper –

+3

@Arjang Quite complesso ... probabilmente molto complesso ... Prova resharper. Altrimenti non so nemmeno dove inizierei ... Forse una regola personalizzata con codeanalysis – xanatos

risposta

5

Quello che potreste fare è programmare Roslyn, il (non così) nuovo figo ragazzo in città. Permette di analizzare progetti C# (o VB.NET) abbastanza facilmente. Quindi puoi visitare i nodi rilevati e controllare cosa vuoi veramente controllare. Rilevare i letterali magici per una macchina non è sempre così facile come sembra per un umano. Ad esempio, 1 è davvero un numero magico? Personalmente ritengo che non lo sia, ma 2 è più sospetto ...

In ogni caso, ecco un piccolo esempio che fa una buona parte del lavoro credo, ma potrebbe/dovrebbe essere migliorato, forse per adattare il tuo business esatto bisogni o regole (che è molto interessante).

Nota Roslyn può anche essere utilizzato direttamente nel contesto di Visual Studio, quindi è possibile trasformare questo esempio in una cosiddetta diagnostica (un'estensione di Visual Studio) che può aiutare a vivere direttamente dall'interno dell'IDE. Ci sono campioni per questo: 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; 
    } 
} 

Se si esegue questo piccolo programma (ho anche fornito metodi di supporto da utilizzare sulla soluzione o progetti) in uscita, lo farà questo:

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" 
0

I avere un codice che può trovare numeri magici e stringhe non costanti codificate. Può essere che può aiutare qualcuno -

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

Il file txt che contiene i nomi di file da ignorare conterrebbe valori come -

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

indicare il nome della soluzione nel percorso soluzione ed eseguire questo. Questo genererà file txt per te con tutte le stringhe hard coded e numeri magici.

Edit:

Per compilare il progetto, è necessario installare il pacchetto Microsoft.CodeAnalysis NuGet nel vostro progetto console app:

Install-Package Microsoft.CodeAnalysis -Pre 

Ecco la lista completa dei riferimenti si dovrebbe avere nella vostra 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

Alcune classi sono mancanti, come SyntaxKind, ExpressionSynt ascia, ecc ... quindi non può essere usato – Latrova