Created
March 16, 2019 21:02
-
-
Save detunized/d02a0640986f44243dc01e5f50f42e74 to your computer and use it in GitHub Desktop.
Find a code pattern in C# files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (C) 2019 Dmitry Yakimenko (detunized@gmail.com). | |
// Licensed under the terms of the MIT license. See LICENCE for details. | |
using System; | |
using System.IO; | |
using System.Linq; | |
using Microsoft.CodeAnalysis; | |
using Microsoft.CodeAnalysis.CSharp; | |
using Microsoft.CodeAnalysis.CSharp.Syntax; | |
namespace NunitToXunit | |
{ | |
using static SyntaxFactory; | |
public static class Ast | |
{ | |
private class Matcher | |
{ | |
public bool Match(SyntaxNode code, SyntaxNode pattern) | |
{ | |
// A placeholder matches anything | |
if (IsPlaceholder(pattern)) | |
return true; | |
if (code.GetType() != pattern.GetType()) | |
return false; | |
switch (code) | |
{ | |
case ArgumentSyntax c: | |
{ | |
var p = (ArgumentSyntax)pattern; | |
return Match(c.Expression, p.Expression); | |
} | |
case ArgumentListSyntax c: | |
{ | |
var p = (ArgumentListSyntax)pattern; | |
return Match(c.OpenParenToken, p.OpenParenToken) | |
&& Match(c.Arguments, p.Arguments) | |
&& Match(c.CloseParenToken, p.CloseParenToken); | |
} | |
case IdentifierNameSyntax c: | |
{ | |
var p = (IdentifierNameSyntax)pattern; | |
return Match(c.Identifier, p.Identifier); | |
} | |
case InvocationExpressionSyntax c: | |
{ | |
var p = (InvocationExpressionSyntax)pattern; | |
return Match(c.Expression, p.Expression) | |
&& Match(c.ArgumentList, p.ArgumentList); | |
} | |
case LiteralExpressionSyntax c: | |
{ | |
var p = (LiteralExpressionSyntax)pattern; | |
return Match(c.Token, p.Token); | |
} | |
case MemberAccessExpressionSyntax c: | |
{ | |
var p = (MemberAccessExpressionSyntax)pattern; | |
return Match(c.Expression, p.Expression) | |
&& Match(c.Name, p.Name); | |
} | |
case GenericNameSyntax c: | |
{ | |
var p = (GenericNameSyntax)pattern; | |
return Match(c.Identifier, p.Identifier) | |
&& Match(c.TypeArgumentList, p.TypeArgumentList); | |
} | |
case TypeArgumentListSyntax c: | |
{ | |
var p = (TypeArgumentListSyntax)pattern; | |
return Match(c.LessThanToken, p.LessThanToken) | |
&& Match(c.Arguments, p.Arguments) | |
&& Match(c.GreaterThanToken, p.GreaterThanToken); | |
} | |
default: | |
return false; | |
} | |
} | |
// | |
// Private | |
// | |
private bool Match<T>(SeparatedSyntaxList<T> code, SeparatedSyntaxList<T> pattern) where T : SyntaxNode | |
{ | |
if (code.Count != pattern.Count) | |
return false; | |
for (var i = 0; i < code.Count; i++) | |
if (!Match(code[i], pattern[i])) | |
return false; | |
return true; | |
} | |
private bool Match(SyntaxToken code, SyntaxToken pattern) | |
{ | |
if (IsPlaceholder(pattern.Text)) | |
return true; | |
if (code.ValueText == pattern.ValueText) | |
return true; | |
return false; | |
} | |
private bool IsPlaceholder(SyntaxNode pattern) | |
{ | |
return pattern is IdentifierNameSyntax i | |
&& IsPlaceholder(i.Identifier.Text); | |
} | |
private bool IsPlaceholder(string name) | |
{ | |
if (name == "_") | |
return true; | |
return false; | |
} | |
} | |
public static ExpressionSyntax Parse(string pattern) | |
{ | |
return ParseExpression(pattern); | |
} | |
public static bool Match(SyntaxNode code, SyntaxNode pattern) | |
{ | |
return new Matcher().Match(code, pattern); | |
} | |
} | |
static class Program | |
{ | |
static void FindMatches(string filename, string pattern) | |
{ | |
var sourceAst = CSharpSyntaxTree.ParseText(File.ReadAllText(filename)); | |
var patternAst = Ast.Parse(pattern); | |
var nodes = sourceAst.GetRoot().DescendantNodes().OfType<ExpressionSyntax>(); | |
foreach (var e in nodes) | |
{ | |
if (Ast.Match(e, patternAst)) | |
{ | |
var line = e.GetLocation().GetLineSpan().StartLinePosition.Line; | |
var code = e.NormalizeWhitespace(); | |
Console.WriteLine($" {line}: {code}"); | |
} | |
} | |
} | |
static void Main(string[] args) | |
{ | |
if (args.Length < 2) | |
{ | |
Console.WriteLine("USAGE: find-code pattern files..."); | |
return; | |
} | |
var pattern = args[0]; | |
Console.WriteLine($"Looking for '{pattern}'"); | |
foreach (var f in args.Skip(1)) | |
{ | |
Console.WriteLine(f); | |
FindMatches(f, pattern); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment