Go lintpack: менеджер компонованих лінтер

 

lintpack — це утиліта для складання лінтер (статичних аналізаторів), які написані з використанням наданого API. На основі нього зараз переписується знайомий деяким статичний аналізатор go-critic.

Сьогодні ми детальніше розберемо що таке lintpack з точки зору користувача.

На початку був go-critic…

go-critic починався як експериментальний проект, який був пісочницею для прототипування практично будь-яких ідей в області статичного аналізу для Go.

Приємним здивуванням було те, що деякі люди дійсно відправляли реалізації детекторів тих чи інших проблем в коді. Все було під контролем, поки не почав накопичуватися технічний борг, усувати який було практично нікому. Люди приходили, додавали перевірку, а потім зникали. Хто після цього має виправляти помилки і допрацьовувати реалізацію?

Знаменною подією була пропозиція додати перевірки, що потребують додаткової конфігурації, тобто такі, які залежать від локальних для проекту домовленостей. Прикладом є виявлення наявності copyright заголовка у файлі (license header) по особливому шаблоном або заборона імпорту деяких пакетів з пропозицією заданої альтернативи.

Іншою трудністю була розширюваність. Відправляти свій код в чужій репозиторій зручно не кожному. Декому хотілося динамічного підключення своїх перевірок, щоб не потрібно було змінювати вихідні коди go-critic.

Резюмуючи, ось проблеми, які стояли на шляху розвитку go-critic:

  • Вантаж складності. Занадто багато підтримувати, наявність безхазяйного коду.
  • Низький середній рівень якості. experimental означав “майже готова до використання”, так і “краще не запускати взагалі”.
  • Іноді важко приймати рішення про включення перевірки в go-critic, а відхиляти їх суперечить вихідної філософії проекту.
  • Різні люди бачили go-critic по-різному. Більшості хотілося мати його у вигляді CI линтера, який йде в поставці з gometalinter.

Щоб хоч якось обмежити кількість різночитань і незбіжних інтерпретацій проекту, був написаний маніфест.Якщо вам хочеться додаткового історичного контексту і ще більше роздумів на тему категоризації статичних аналізаторів, можете послухати запис GoCritic — новий статичний аналізатор для Go. В той момент lintpack ще не існував, але частина ідей народилася саме в той день, після доповіді.

А що якщо б нам не потрібно було зберігати всі перевірки в одному сховищі?

Зустрічайте — lintpack

 

go-critic складається з двох основних компонентів:

 

  1. Реалізація самих перевірок.
  2. Програма, яка завантажує перевіряються Go пакети і запускає на них перевірки.

Наша мета: мати можливість зберігати перевірки для линтера в різних репозиторіях і зібрати їх воєдино, коли це необхідно.

 

lintpack робить саме це. Він визначає функції, що дозволяють описувати свої перевірки таким чином, що їх потім можна запускати через створюваний лінтер.

Пакети, які реалізовані з використанням lintpack як фреймворку, будемо називати lintpack-сумісними або lintpack-compatible пакетами.

Якщо б сам go-critic був реалізований на основі lintpack, всі перевірки можна було б розділити на кілька репозиторіїв. Одним з варіантів поділу може бути наступний:

  1. Основний набір, куди потрапляють всі стабільні і підтримувані перевірки.
  2. contrib репозиторій, де лежить код, який або занадто експериментальний, або не має меинтейнера.
  3. Щось на зразок go-police, де можуть знаходиться ті самі настроюються під конкретний проект перевірки.
Читайте також  Поради щодо запуску мобільної гри: Частина 2, Глобальний запуск

 

Перший пункт має особливо важливе значення у зв’язку з інтеграцією go-critic в golangci-lint.

 

Якщо залишатися на рівні go-critic, то для користувачів практично нічого не змінилося. lintpack створює майже ідентичний раніше лінтер, а golangci-lint інкапсулює всі окремі деталі реалізації.

 

Але дещо все-таки змінилося. Якщо на основі lintpack будуть створюватися нові линтеры, у вас з’явиться більш багатий вибір готових діагностик для генерації линтера. На хвилину уявимо, що це так, і в світі існує більше 10 різних наборів перевірок.

Quick start

 

Для початку, потрібно встановити сам lintpack:

 

# lintpack буде встановлено у `$(go env GOPATH)/bin`.
go get -v github.com/go-lintpack/lintpack/...

 

Створимо лінтер, використовуючи тестовий пакет з lintpack:

 

lintpack build -o mylinter github.com/go-lintpack/lintpack/checkers

 

В набір входить panicNil, який знаходить в коді panic(nil) і просити виконати заміну на щось непомітне, оскільки в іншому випадку recover() не зможе підказати, був викликаний panic з nil аргументом, або паніки не було зовсім.

Приклад з panic(nil)

Код нижче намагається описати значення, отримане з recover():

 

r := recover()
fmt.Printf("%T, %vn, r, r)

 

Результат буде ідентичним для panic(nil) і для програми, яка не панікує.

Запускається приклад описуваного поведінки.

Запускати лінтер можна на окремих файлах, аргументами типу ./... або пакетах (за їх import шляху).

 

./mylinter check bytes
$GOROOT/src/bytes/buffer_test.go:276:3: panicNil: panic(nil) calls are discouraged

 

# Далі робиться припущення, що go-lintpack є під вашим $GOPATH.
mylinter=$(pwd)/mylinter

cd $(go env GOPATH)/src/github.com/go-lintpack/lintpack/checkers/testdata

$mylinter check ./panicNil/
./panicNil/positive_tests.go:5:3: panicNil: panic(nil) calls are discouraged
./panicNil/positive_tests.go:9:3: panicNil: panic(interface{}(nil)) calls are discouraged

 

За замовчуванням ця перевірка також реагує на panic(interface{}(nil)). Щоб змінити цю поведінку, потрібно встановити значення skipNilEfaceLit в true. Зробити це можна через командний рядок:

 

$mylinter check -@panicNil.skipNilEfaceLit=true ./panicNil/
./panicNil/positive_tests.go:5:3: panicNil: panic(nil) calls are discouraged

usage для cmd/lintpack та генерованого линтера

І lintpack, і генерується лінтер, використовують перший аргумент для вибору підкоманди. Список доступних встановлення та прикладів їх запуску можна отримати викликавши утиліту без аргументів.

lintpack
not enough arguments, expected sub-command name

Supported sub-commands:
 build - build linter from made of lintpack-compatible packages
 $ lintpack build -help
 $ lintpack build -o gocritic github.com/go-critic/checkers
 $ lintpack build -linter.version=v1.0.0 .
 version - print version lintpack
 $ lintpack version

 

Припустимо, ми назвали створений лінтер ім’ям gocritic:

 

./gocritic
not enough arguments, expected sub-command name

Supported sub-commands:
 check - run linter over specified targets
 $ linter check -help
 $ linter check -disableTags=none strings bytes
 $ linter check -enableTags=diagnostic ./...
 version - print version linter
 $ linter version
 doc - get installed checkers documentation
 $ linter doc -help
 $ linter doc
 $ linter doc checkerName

 

Для деяких встановлення доступний прапор -help, який надає додаткову інформацію (я вирізав деякі занадто широкі рядки):

Читайте також  libGDX і почуття

 

./gocritic check -help
# Інформація про всіх доступних прапорах.

Документація встановлених перевірок

Відповідь на питання “як дізнатися про те самому параметрі skipNilEfaceLit?” — read the fancy manual (RTFM)!

 

Вся документація про встановлені перевірках знаходиться всередині mylinter. Доступна ця документація через потім doc:

 

# Виводить список всіх встановлених перевірок:
$mylinter doc
panicNil [diagnostic]

# Виводить детальну документацію щодо запитуваної перевірці:
$mylinter doc panicNil
panicNil checker documentation
URL: github.com/go-lintpack/lintpack
Tags: [diagnostic]

Detects panic(nil) calls.

Such panic calls are hard to handle during recover.

Non-compliant code:
panic(nil)

Compliant code:
panic("something meaningful")

Checker parameters:
 -@panicNil.skipNilEfaceLit bool
 whether to ignore interface{}(nil) arguments (default false)

 

Подібно підтримки шаблонів в go list -f, ви можете передати рядок шаблон, яка відповідає за формат виводу документації, що може бути корисним при складанні markdown документів.

Де шукати перевірки для встановлення?

Для спрощення пошуку корисних наборів перевірок є централізований список lintpack-сумісних пакетів: https://go-lintpack.github.io/.

Ось деякі із списку:

  • https://github.com/go-critic/go-critic/checkers
  • https://github.com/go-critic/checkers-contrib
  • https://github.com/Quasilyte/go-police

 

Цей список періодично оновлюється і він відкритий для заявок на додавання. Будь-який з цих пакетів може використовуватися для створення линтера.

 

Команда нижче створює лінтер, який містить всі перевірки зі списку вище:

 

# Спочатку потрібно переконатися, що вихідні коди всіх перевірок
# доступні для Go компілятора.
go get -v github.com/go-critic/go-critic/checkers
go get -v github.com/go-critic/checkers-contrib
go get -v github.com/Quasilyte/go-police

# build приймає список пакетів.
lintpack build 
 github.com/go-critic/go-critic/checkers 
 github.com/go-critic/checkers-contrib 
 github.com/Quasilyte/go-police

 

lintpack build включає всі перевірки на етапі компіляції, одержуваний лінтер може бути розміщений в оточенні, де відсутні вихідні коди реалізації встановлених діагностик, все як зазвичай при статичному лінкування.

Динамічне підключення пакетів

У додаток до статичної збірці є можливість завантажувати додатки, що надають додаткові перевірки.

 

Особливістю є те, що реалізація чекера не знає, чи будуть її використовувати при статичному компіляції або будуть довантажувати у вигляді плагіна. Ніяких змін в коді не потрібно.

Припустимо, ми хочемо додати panicNil в лінтер, але ми не маємо можливості перебудувати його з усіх джерел, які використовувалися при першій компіляції.

  1. Створюємо linterPlugin.go:

 

package main

// Якщо потрібно включити плагін більше одного набору перевірок,
// просто додайте необхідні import'и.
import (
 _ "github.com/go-lintpack/lintpack/checkers"
)
  1. Збираємо динамічну бібліотеку:
go build -buildmode=plugin -o linterPlugin.so linterPlugin.go

 

  1. Запускаємо лінтер з параметром -pluginPath:
./linter check -pluginPath=linterPlugin.so bytes

Попередження: Підтримка динамічних модулів реалізована через пакет plugin, який не працює на Windows.

Прапор -verbose може допомогти розібратися якась перевірка включена або виключена, а, найголовніше, там буде відображено який з фільтрів відключив перевірку.

Приклад з -verbose

Зверніть увагу, що panicNil відображається у списку включених перевірок. Якщо ми приберемо аргумент -pluginPath, це перестане бути істиною.

./linter check -verbose -pluginPath=./linterPlugin.so bytes
 debug: appendCombine: disabled by tags (-disableTags)
 debug: boolExprSimplify: disabled by tags (-disableTags)
 debug: builtinShadow: disabled by tags (-disableTags)
 debug: commentedOutCode: disabled by tags (-disableTags)
 debug: deprecatedComment: disabled by tags (-disableTags)
 debug: docStub: disabled by tags (-disableTags)
 debug: emptyFallthrough: disabled by tags (-disableTags)
 debug: hugeParam: disabled by tags (-disableTags)
 debug: importShadow: disabled by tags (-disableTags)
 debug: indexAlloc: disabled by tags (-disableTags)
 debug: methodExprCall: disabled by tags (-disableTags)
 debug: nilValReturn: disabled by tags (-disableTags)
 debug: paramTypeCombine: disabled by tags (-disableTags)
 debug: rangeExprCopy: disabled by tags (-disableTags)
 debug: rangeValCopy: disabled by tags (-disableTags)
 debug: sloppyReassign: disabled by tags (-disableTags)
 debug: typeUnparen: disabled by tags (-disableTags)
 debug: unlabelStmt: disabled by tags (-disableTags)
 debug: wrapperFunc: disabled by tags (-disableTags)
 debug: appendAssign is enabled
 debug: assignOp is enabled
 debug: captLocal is enabled
 debug: caseOrder is enabled
 debug: defaultCaseOrder is enabled
 debug: dupArg is enabled
 debug: dupBranchBody is enabled
 debug: dupCase is enabled
 debug: dupSubExpr is enabled
 debug: elseif is enabled
 debug: flagDeref is enabled
 debug: ifElseChain is enabled
 debug: panicNil is enabled
 debug: regexpMust is enabled
 debug: singleCaseSwitch is enabled
 debug: sloppyLen is enabled
 debug: switchTrue is enabled
 debug: typeSwitchVar is enabled
 debug: underef is enabled
 debug: unlambda is enabled
 debug: unslice is enabled
# ... результат роботи линтера.
Порівняння з gometalinter і golangci-lint

Щоб уникнути плутанини, варто описати основні відмінності між проектами.

gometalinter і golangci-lint в першу чергу інтегрують інші, часто дуже по-різному реалізовані, линтеры, надають до них зручний доступ. Вони націлені на кінцевих користувачів, які будуть використовувати статичні аналізатори.

lintpack спрощує створення нових лінтер, надає фреймворк, який робить різні пакети, реалізовані на його основі, сумісними в межах одного виконуваного файлу. Ці перевірки (для golangci-lint) або виконуваний файл (для gometalinter) далі можуть бути вбудовані в вищезазначені мета-линтеры.

 

Припустимо, якась з lintpack-сумісних перевірок є частиною golangci-lint. Якщо існує якась проблема, пов’язана із зручністю його використання — це може бути зоною відповідальності golangci-lint, але якщо мова йде про помилку у реалізації самої перевірки, то це проблема авторів перевірки, lintpack екосистеми.

Іншими словами, ці проекти вирішують різні проблеми.

А що там з go-critic?

Процес портування go-critic на lintpack вже майже завершено. work-in-progress можна знайти в репозиторії go-critic/checkers. Після завершення переходу, перевірки будуть переміщені в go-critic/go-critic/checkers.

 

# Установка go-critic до:
go get -v github.com/go-critic/go-critic/...

# Установка go-critic після:
lintpack -o gocritic github.com/go-critic/go-critic/checkers

 

Великого сенсу використовувати go-critic поза golangci-lint немає, а ось lintpack може дозволити встановити ті перевірки, які не входять в набір go-critic. Наприклад, це можуть бути діагностики, написані вами.

Продовження слідує

Як створювати свої lintpack-сумісні перевірки ви дізнаєтеся в цій статті.

Там же ми розберемо які переваги ви отримуєте при реалізації свого линтера на основі lintpack порівняно з реалізацією з чистого аркуша.

Сподіваюся, у вас з’явився апетит до нових перевірок для Go. Дайте знати, як статичного аналізу стане занадто багато, будемо оперативно вирішувати цю проблему разом.

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *