22 марта 2010 в 12:38

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.
+15
825
13
planerist 15,5

Комментарии (20)

+6
flashnik, #
> Вы можете воспользоваться регулярными выражениями и попытаться сделать Find Text, но обычно C# разработчики не очень хорошо разбираются в этом вопросе и могут совершить много ошибок.
Жестко, однако, Вы прошлись по приверженцам C#.
+10
sse, #
C# — это не perl, можно поработать в нескольких проектах и ни разу не столкнуться с Regex. Это нормально.
+6
flashnik, #
Это, конечно, так (знаю на собственном опыте), но мне кажется, что регэкспы в любом случае входят в кругозор программиста.
+3
sse, #
«Входят в кругозор» и «хорошо разбираются» — это все же разные вещи.
Меня, например, лоботомирует задача по длинному однострочному регэкспу сказать, что он ищет. Если еще и бэкреференсы есть, вообще жестя. Не хватало еще этот аццкий ад тащить в окно поиска студии :)
+1
alexey_uzhva, #
Хорошо разбираться — это значит делать при помощи них какой-нибудь разбор или сложную замену, валидацию пользовательского ввода и тому подобное.

А для поиска-замены «входить в кругозор» более чем достаточно.
0
planerist, #
напишите, пожалуйста, regexp для любого примера из статьи. лучше, конечно, для поиска if, которые могут быть сконвертированы в тернарный оператор.
0
alexey_uzhva, #
Боюсь, что красоты, конечно, не получится — выйдет порядочная дрянь, да… тут вы правы… посмотрел сначала в комментарии, потом в статью. Признаю.

Но может, тогда нет смысла про регекспы и писать? Ведь по сути описываемый инструмент гораздо ближе к рефакторингу чем к поиску-замене, а это уже совсем другая песня…
0
planerist, #
это именно поиск и замена. поиск сложных конструкций с учетом семантики языка и замена на такие же сложные конструкции. регулырные выражения — это то, как раньше неудачно и монструозно иногда решали это задачу.

и к рефакторингам это не имеет никакого отношения. рефакторинги — это в первую очередь сложная логика по сохранению функциональности кода. Search With Pattern — этого не делает и для этого не задумывался.
+7
nZeus, #
Здорово! Скоро JetBrains свою IDE напишет для .net-разработки! :))
–1
nZeus, #
(это я так, мечтаю)
+2
IBB4, #
Увы, но это практически не реально. ((
Дело в том, что любой полноценный солюшен — это не только шарповский проект, но еще и всякая дополнительная фигня, типа asp.Net-а, DB проектов, тестов, разных MVC, WPF, WF, WCF, офиса, сервилада, диаграм и еще байт знает какой лабуды, включая поддержку других языков, так как и смешанные проекты тоже не редкость. И чтобы пользовались новой IDE, она должна все это поддерживать иначе она будет никому не нужна. Масштабы такого проекта переоценить сложно.
Если кто не в курсе, MS в свое время тоже не потянул создание альтернативной студии. Код текущей VS тянется с середины девяностых, и в районе появления 05 студии было принято решение попробовать написать все заново. Ядро студии назвалось проект Nautilus, а сама студия имела кодовое название Гаваи — это то, что должна была представлять из себя VS 2010.
Но в итоге, в районе 08-года проект закрыли и из всего, что было в гаваях, в 2010-й студии только редактор, ну и еще кое что по мелочи…
Я, конечно, верю в ребят из JB, но боюсь, что проект просто не окупится.
0
mezastel, #
А зачем? Чем вас не устраивает vs2010?
0
nicity, #
Полагаю, что значение текста, которое соотнесено с плейсхолдером, всё-таки можно проверить на совпадение с регулярным выражением :)
0
Nashev, #
Крутая штука. Хочу такое в Delphi!

А вообще, похоже к этому постепенно придут все IDE, как пришли к подсветке синтаксиса.
0
mezastel, #
Классно, конечно, но налицо небольшой оверинжиниринг.
0
planerist, #
раскройте мысль, пожалуйста (про оверинжиниринг)
0
mezastel, #
ну если коротко, то в решарпере очень много фич. некоторые из них killer, некоторыми пользуешься раз в год или никогда. конкретно та фича которую вы описали тут очень мощная, я не спорю, но не столько востребованная сколько например пресловутый refactor→rename
0
planerist, #
вы не можете оценить ее воссребованность исходя только из вашего личного опыта и вашего стиля программирования. команда R# имеет уже пачку реквестов на расширение этой фичи, потому что люди знают как и почему они будут ее использовать.

но исходя из вашей логики после имплементации rename можно было уже вообще продукт не развивать. в этом есть разумное зерно, особенно если вы не пытаетесь решать проблемы пользователей.

но value от фичи нельзя измерять частотою ее использования. частота — это buzz, не более.
0
mezastel, #
надо наверное на хабре опрос вывесить :)
0
planerist, #
такие опросы никогда не отражают действительность, вы же понимаете, пользователи никогда не знают, что они хотят

Только зарегистрированные пользователи могут оставлять комментарии.
Войдите, пожалуйста.