Skip to content

Instantly share code, notes, and snippets.

@detunized
Created March 16, 2019 21:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save detunized/d02a0640986f44243dc01e5f50f42e74 to your computer and use it in GitHub Desktop.
Save detunized/d02a0640986f44243dc01e5f50f42e74 to your computer and use it in GitHub Desktop.
Find a code pattern in C# files
// 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