Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Как прокачать линтер

Как прокачать линтер

Python Meetup Chelyabinsk #5

Avatar for Maxim Mazaev

Maxim Mazaev

March 23, 2019
Tweet

More Decks by Maxim Mazaev

Other Decks in Programming

Transcript

  1. • 1 млн уникальных пользователей в сутки • 10x разработка

    за 2014-2019 • 100+ разработчиков • 30 питонистов • Сотни строк нового кода каждый день О ЦИАН
  2. Проблемы с код-ревью • Есть внутренние договоренности по стилю кода.

    Линтер не покрывает • Линтер не покрывает наши договоренности по стилю.
  3. Решение А не написать ли нам свой линтер? Нет, у

    нас уже есть pylint • он удобный • он встроен в наши процессы
  4. 2015 Sergey # TODO: выпилить (не выпилили) 2015 Misha #

    TODO: Убрать после успешного релиза (не убрали) 2017 Vadim # TODO: после эксперимента удалим (не удалили) ¯\_(ツ)_/¯
  5. Пример №1 Проблема: В коде много комментариев «TODO» • Висят

    годами • Не выполняются • Копится техдолг
  6. class TodoIssueChecker(BaseChecker): __implements__ = IRawChecker Будем итерироваться по строкам модуля

    («сырой» чекер) Решение. Пишем свой чекер для Pylint
  7. msgs = { 'С9999': ('Комментарий с TODO без ссылки на

    задачу', 'issue-code-in-todo', 'Длинное описание')} Код и мнемоничеcкое название чекера Пишем свой чекер
  8. С1234 [C]onvention [W]arning Произвольные 4 цифры [E]rror (уникальные в рамках

    pylint) [F]atal [R]efactoring Пишем свой чекер. Код сообщения
  9. msgs = { 'С9999': ('Комментарий с TODO без ссылки на

    задачу', 'issue-code-in-todo', 'Длинное описание')} # pylint: disable=C9999 # pylint: disable=issue-code-in-todo Пишем свой чекер
  10. def process_module(self, node): with node.stream() as stream: for (lineno, line)

    in enumerate(stream): if has_todo_without_issue_code(line): self.add_message( 'issue-code-in-todo', line=lineno ) Пишем свой чекер Выводим предупреждение Ходим по строкам
  11. def register(linter: PyLinter) -> None: linter.register_checker( TodoIssueChecker(linter) ) * Модуль

    чекера должен быть в PYTHONPATH Регистрируем чекер *
  12. $ cat work.py # TODO: Удалю через неделю, честно-честно! $

    pylint work.py --load-plugins todo_checker ... И запускаем!
  13. 1. Инициализация плагинов 1. Импортируем все модули, где есть плагины

    2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
  14. $ cat work.py # TODO: Удалю через неделю, честно-честно! $

    pylint work.py --load-plugins todo_checker C: 0, 0: Комментарий с TODO без ссылки на задачу (issue-code-in-todo) Результат
  15. Преобразование кода в AST. Вызов функции get_offer( 112, deal_type=’sale’, offer_type=’flat’,

    ) Call( func=Name(name='get_offer'), args=[Const(value=112)], keywords=[ Keyword( arg='deal_type', value=Const(value='sale') Keyword( arg='offer_type', value=Const(value='flat')) ]))]
  16. Задача. • Найти все ноды Call (вызов функции) • Посчитать

    количество аргументов • Проверить отсутствие позиционных аргументов Call( func=Name(name='get_offer'), args=[Const(value=1298880)], keywords=[ ... ]))]
  17. msgs = { 'С9191': ('Краткое описание', 'keyword-only-args', 'Длинное описание')} Код

    и мнемоничеcкое название чекера Пишем чекер на основе AST
  18. def visit_call(self, node: Call) … visit_<Имя_ноды> вызывается при заходе на

    ноду leave_<Имя_ноды> при уходе с ноды Пишем чекер на основе AST
  19. def visit_call(self, n: Node): if n.args and len(n.args + n.keywords)

    > 2: self.add_message( 'keyword-only-args', node=node ) Пишем чекер на основе AST
  20. 1. Инициализация плагинов 1. Импортируем все модули, где есть плагины

    2. module.register(self) <- Регистрируем + Проверяем, что параметры валидны + Регистрируем опции, сообщения и отчеты
  21. Почему Astroid, а не AST (stdlib) 1. Разбирает дерево с

    помощью typed_ast (c поддержкой тайпхинтов из PEP 484)
  22. Почему Astroid, а не AST (stdlib) from module import Entity

    def foo(bar): # type: (Entity) -> None return
  23. Почему Astroid, а не AST (stdlib) 2. Собирает обратно с

    дополнительными плюшками: from foo import * Импортирует и добавит в locals содержимое foo
  24. Почему Astroid, а не AST (stdlib) 3. Transform plugins •

    Поможем Astroid разобраться в динамической природе питона. • Дополняем AST полезной информацией см. pylint-django
  25. 4. Разобрать чекеры по типам нод 1. У каждого чекера

    находим методы visit_<Имя ноды> leave_<Имя ноды>
  26. 4. Разобрать чекеры по типам нод 2. И сохраняем: _visit_methods

    = dict( <Имя ноды>: [checker1, checker2,… ] ) _leave_methods = dict( <Имя ноды>: [checker1, checker2,… ] )
  27. $ cat work.py get_offers(1, True, deal_type=”sale”) $ pylint work.py --load-plugins

    non_kwargs_checker C: 0, 0: Функция с >2 аргументами вызывается с позиционными аргументами (keyword-only-args) Запускаем чекер!
  28. Лексический анализ кода NAME NAME OP NAME OP NAME OP

    OP NEWLINE def sum(a, b): INDENT NAME NAME OP NAME NEWLINE return a + b DEDENT ENDMARKER
  29. Как Pylint работает с TokenChecker 1. Токенизирует модуль 2. Для

    всех чекеров с интерфейсом ITokenChecker вызывает: process_tokens(tokens)
  30. Выводы Чего добились: • Переложили рутинные проверки на линтер •

    Уменьшили количество отклоненных пулл-реквестов • Повысили качество ревью