Как стать автором
Обновить

ReSharper: поиск кода по паттерну

.NET *
Есть два вида поиска, которые вы часто используете: «Find Text» и «Find Usages». Но ни один из них не позволяет искать сложные языковые конструкции, например, все места в вашем коде, где используется выражение "s == null || s == String.Empty". Вы можете воспользоваться регулярными выражениями и попытаться сделать Find Text, но такие регулярные выражения будут выглядеть монструозно и, наверняка, содержать много ошибок (например, не будут учитвать возможность комментариев в почти каждой точке программы). Очевидно, что для решения этой задачи нужен какой-то другой вид поиска, который бы знал о синтаксисе языка, системе типов и не заставлял бы разработчика изучать какой-то новый синтаксис языка запросов.


Умный поиск


В ReSharper 5.0 появился новый тип поиска «Search With Pattern», который позволяет искать куски кода по шаблону, причем на части этого шаблона можно накладывать ограничения. Например, для выражений можно указать тип, а для аргументов их предполагаемое количество.

Давайте рассмотрим конкретный пример. Будем искать в вашем коде все выражения "enumerable.Count() > 0", где enumerable — это любое выражение типа IEnumerable.
Если бы вы решали эту задачу через Find Usages, то вы бы шали вызовы метода Count(), а соответственно вам пришлось бы глазами просматривать дестяки или сотни вызовов — это утомительно и чревато ошибками. Find Text выглядит несколько лучше, но если подумать о том, что искомое выражение может быть записано с переводами строк, комментариями, а метод Count() в вашем проекте реализуют объекты разных типов, то становится понятно, что и он не подходит.

Откройте окно «Search With Pattern» (Resharper -> Find -> Search With Pattern) и введите в текстовое поле следующий шаблон:

$enumerable$.Count() > 0

Строка $enumerable$ — будет подсвечена красным. Дело в том, что в знаки "$" заключаются имена плейсхолдеров, во время поиска на месте такого плейсхолдера будет ожидаться любой текст, соответствующий заданным ограничениям: типу плейсхолдера и его параметрам. В нашем случае на месте $enumerable$ может быть любое выражение типа IEnumerable. Но для начала нам нужно определить этот плейсхолдер. Для этого нажмите «Add Placeholder», выберите «Expression», в поле «Name» введите "enumerable" (имя плейсхолдера без знаков доллара). В «Expression Type» введите "IEnumerable" (начните вводить имя типа, и решарпер сам подскажет вам варианты). Не забудьте поставить галочку «Or derived type».

Буквально за несколько секунд, без знаний о регулярных выражениях мы создали шаблон для поиска. Но есть еще одна приятная и очень мощная вещь: обратите внимание, что под полем редактирования шаблона есть галочка «Match similar constructs». Если эта галочка установлена, то происходит поиск не только точных совпадений с образцом, но и сематически идентичных конструкций. Например, конструкции "a > 0" и "0 < a" семантически идентичны. В нашем случае эту галочку разумно оставить установленной, ведь вам все равно как результат метода Count() сравнивается с нулем.

Все готово! Теперь можете нажать кнопку «Find» и посмотреть на результаты.

Умная Замена


Такой мощный поиск без функции замены был бы не полным, ведь интересно не просто найти все нехорошие места в программе, но и заменить их правильным кодом. Для этого в окне «Search With Pattern» нажмите кнопку «Replace». Появится поле для ввода паттерна замены. В этом поле можно написать любой корректный с точки зрения языка текст, также можно использовать плейсхолдеры. Введите в это поле:

$enumerable$.Any()

Теперь нажимайте кнопку Replace!

Делаем из паттерна поиска подсветку и QuickFix


Теперь у вас есть шаблон для поиска и шаблон для замены. Логично сделать из этого подсветку и QuickFix. Для этого в окне редактирования паттерна просто нажмите кнопу «Save». Ваш паттерн сохранится в «Patterns Catalogue». Этот каталог можно просто использовать для хранения часто используемых шаблонов, а можно сделать из него мощное средство для создания собственных анализов.

Если вы откроете каталог (ReSharper -> Tools -> Patterns Catalogue), то сможете для своего паттерна настроить настроить текст подсказки, текст, который будет показываться в QuickFix и тип подсветки. Установите для своего паттерна все эти параметры.

Все! Подсветка работает! Теперь весь код, соответствующий вашему шаблону, будет подсвечиваться налету! А на подсветке будет появляться соответствующий QuickFix!

Примеры паттернов поиска


Паттерн поиска, который упрощает выражения:



В этом примере мы использовали плейсхолдеры для типа, выражения и идентификатора. При этом не задали никаких ограничений на них, но зато использовали их в шаблоне замены. Единственный плейсхолдер с ограничением — это $seq$, он ограничен типом IEnumrable.

А вот паттерн, который реализует подсветку и QuickFix «Replace 'if' with '?:'»:



Если у вас есть идеи полезных паттернов, то расскажите про них в комментариях. Это будет полезно для сообщества, а возможно самые интересные паттерны будут включены в следующую поставку ReSharper.

Пишем расширения к ReSharper легко и быстро


Но это еще не все. На самом деле механизм, который выполняет сопоставление с образцом, намного мощнее, чем это видно конечному пользователю. Текущие ограничения связаны с тем, что команде ReSharper пока не понятно, как правильно выразить в UI те или иные аспекты. Например, в текущей реализации нельзя найти конструкцию, в которой что-то отсутствует (например, вызов метода без какой-либо проверки, или метод определенного вида, но без атрибута). Но это можно сделать через API.

Более того, если вы пишете подсветку или QuickFix с ипользованием этого API, то вы здорово экономите свое время, т.е. вам не приходится серьезно разбираться в модели исходного кода и прочих тонкостях. Вы описываете образец в терминах синтаксиса C#, задаете параметры и получаете результат, например для «Replace 'if' with '?:'» достаточно написать такой код:

var myMatcher = searcherFactory
     .CreatePattern("if ($condition$) { $x$ = $expr1$; } else { $x$ = $expr2$; }")
     .AddExpressionPlaceholder("condition")
     .AddIdentifierPlaceholder("x")
     .AddExpressionPlaceholder("expr1")
     .AddExpressionPlaceholder("expr2")
     .CreateStatementMatcher();


* This source code was highlighted with Source Code Highlighter.


Очень просто, а главное понятно без пояснений, и не требует знаний о внутренностях ReSharper. Если бы вы стали писать этот код без использования Search With Pattern API, то вам бы потребовалось знание о том как устроено синтаксическое дерево, об интерфейсе IIfStatement, о том, чем IExpression отличается от IReferenceExpression, о том как сравнивать выражения на эквивалентность (вам надо сравнить два вхождения выражения $x$), и много других сложных вещей.

Вы ищете код по образцу, делаете дополнительные проверки (например, невыразимые через Search With Pattern API) и можете развешивать подсветки!

Search With Pattern API доступен через интерфейс StructuralSearchEngine. Если будет достаточное количество заинтересованных людей, то я могу в следующей статье привести небольшой пример того, как можно с его помощью легко создавать собственные подсветки и QuickFix.
Теги:
Хабы:
Всего голосов 39: ↑27 и ↓12 +15
Просмотры 3.9K
Комментарии Комментарии 20