
Nous annonçons un nouveau linter (analyseur statique) pour Go , qui est également un bac à sable pour prototyper vos idées dans le monde de l'analyse statique.
go-critique est construit autour des constats suivants:
- Mieux vaut avoir une implémentation «assez bonne» du test que de ne pas l'avoir du tout
- Si le chèque est controversé, cela ne signifie pas qu'il ne peut pas être utile. Nous marquons comme «opiniâtres» et versons
- Il est généralement plus difficile d'écrire un linter à partir de zéro que d'ajouter une nouvelle vérification à un framework existant si le framework lui-même est facile à comprendre.
Dans cet article, nous examinerons l'utilisation et l'architecture de go-critique, certaines des vérifications qui y sont implémentées , et décrirons également les principales étapes pour y ajouter notre fonction d'analyseur.
Démarrage rapide
$ cd $GOPATH $ go get -u github.com/go-critic/go-critic/... $ ./bin/gocritic check-package strings $GOROOT/src/strings/replace.go:450:22: unslice: could simplify s[:] to s $GOROOT/src/strings/replace.go:148:2: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:156:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:219:3: elseif: should rewrite if-else to switch statement $GOROOT/src/strings/replace.go:370:1: paramTypeCombine: func(pattern string, value string) *singleStringReplacer could be replaced with func(pattern, value string) *singleStringReplacer $GOROOT/src/strings/replace.go:259:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/replace.go:264:2: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/strings.go:791:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:800:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:809:1: paramTypeCombine: func(s string, cutset string) string could be replaced with func(s, cutset string) string $GOROOT/src/strings/strings.go:44:1: unnamedResult: consider to give name to results $GOROOT/src/strings/strings.go:61:1: unnamedResult: consider to give name to results $GOROOT/src/strings/export_test.go:28:3: rangeExprCopy: copy of r.mapping (256 bytes) can be avoided with &r.mapping $GOROOT/src/strings/export_test.go:42:1: unnamedResult: consider to give name to results
(Le formatage des avertissements a été modifié; les originaux sont disponibles dans l' essentiel .)
L'utilitaire gocritic peut vérifier des packages individuels par leur chemin d'importation ( check-package
), et également parcourir récursivement tous les répertoires ( check-project
). Par exemple, vous pouvez vérifier l'intégralité de $GOROOT
ou $GOPATH
avec une seule commande:
$ gocritic check-project $GOROOT/src $ gocritic check-project $GOPATH/src
Il existe un support pour la «liste blanche» pour les contrôles afin de lister explicitement quels contrôles doivent être effectués (indicateur -enable
). Par défaut, toutes les vérifications qui ne sont pas marquées avec l'icône Experimental
ou VeryOpinionated
sont VeryOpinionated
.
Des intégrations dans golangci-lint et gometalinter sont prévues .
Comment tout a commencé
Lors de la prochaine révision du code du projet Go, ou lors de l'audit d'une bibliothèque tierce, vous pouvez constater les mêmes problèmes à maintes reprises.
À votre regret, il n'a pas été possible de trouver un linter qui pourrait diagnostiquer cette classe de problèmes.
Votre première étape peut être une tentative de catégoriser le problème et de contacter les auteurs du linter existant, en leur suggérant d'ajouter une nouvelle vérification. Les chances que votre proposition soit acceptée dépendent fortement du projet et peuvent être assez faibles. De plus, très probablement, des mois d'attente suivront.
Mais que se passe-t-il si la vérification est complètement ambiguë et peut être perçue par quelqu'un comme trop subjective ou insuffisamment précise?
Peut-être qu'il est logique d'essayer d'écrire ce chèque vous-même?
go-critic
existe pour devenir un foyer de tests expérimentaux plus faciles à implémenter par nous-mêmes que de les attacher à des analyseurs statiques existants. Le dispositif go-critic
lui go-critic
même minimise la quantité de contexte et d'actions nécessaires pour ajouter une nouvelle vérification - nous pouvons dire que vous devez ajouter un seul fichier (sans compter les tests).
Fonctionnement de Go-Critic
Un critique est un ensemble de règles qui décrivent les propriétés de vérification et des micro- vérificateurs qui mettent en œuvre l'inspection du code pour la conformité à une règle.
Une application qui incorpore un linter (par exemple, cmd / gocritic ou golangci-lint ) reçoit une liste des règles prises en charge, les filtre d'une manière spécifique, crée une fonction de vérification pour chaque règle sélectionnée et lance chacune d'elles sur le package à l'étude.
L'ajout d'un nouveau vérificateur se résume à trois étapes principales:
- Ajout de tests.
- La mise en œuvre de la vérification elle-même.
- Ajout de documentation pour le linter.
Nous allons passer en revue tous ces points en utilisant l'exemple de règle captLocal , qui nécessite l'absence de noms locaux commençant par une majuscule.

Ajout de tests
Pour ajouter des données de test pour une nouvelle vérification, vous devez créer un nouveau répertoire dans lint / testdata .
Chacun de ces répertoires doit avoir un fichier positive_tests.go , qui décrit des exemples de code sur lesquels la vérification doit fonctionner. Pour tester l'absence de faux positifs, les tests sont complétés par un code «correct», dans lequel la nouvelle vérification ne devrait pas trouver de problème ( negative_tests.go ).
Exemples:
Vous pouvez exécuter des tests après avoir ajouté un nouveau linter.
Mise en œuvre de la vérification
Créez un fichier avec le nom du vérificateur: lint/captLocal_checker.go
.
Par convention, tous les fichiers de micro-linter ont le suffixe _checker
.
package lint
checkerBase est un type qui doit être incorporé dans chaque vérificateur.
Il fournit des implémentations par défaut, ce qui vous permet d'écrire moins de code dans chaque linter.
Entre autres choses, checkerBase inclut un pointeur sur lint.context
, qui contient des informations de type et d'autres métadonnées sur le fichier en cours de vérification.
Le champ upcaseNames
contiendra une table de noms connus, que nous proposerons de remplacer par la version strings.ToLower(name)
. Pour les noms qui ne figurent pas sur la carte, il sera suggéré de ne pas utiliser de majuscule, mais aucun remplacement correct ne sera fourni.
L'état interne est initialisé une fois pour chaque instance.
La méthode Init()
doit être définie uniquement pour les linters qui doivent effectuer une initialisation préliminaire.
func (c *captLocalChecker) Init() { c.upcaseNames = map[string]bool{ "IN": true, "OUT": true, "INOUT": true, } }
Vous devez maintenant définir la fonction de vérification elle-même.
Dans le cas de captLocal
, nous devons vérifier tous les ast.Ident
locaux qui introduisent de nouvelles variables.
Afin de vérifier toutes les définitions locales de noms, vous devez implémenter une méthode avec la signature suivante dans votre vérificateur:
VisitLocalDef(name astwalk.Name, initializer ast.Expr)
La liste des interfaces visiteurs disponibles se trouve dans le fichier lint / internal / visitor.go .
captLocal
implémente LocalDefVisitor
.
Par convention, les méthodes qui génèrent des avertissements sont généralement présentées dans des méthodes distinctes. Il existe de rares exceptions, mais suivre cette règle est considéré comme une bonne pratique.
Ajout de documentation
Une autre méthode d'implémentation nécessaire est InitDocumentation
:
func (c *captLocalChecker) InitDocumentation(d *Documentation) { d.Summary = "Detects capitalized names for local variables" d.Before = `func f(IN int, OUT *int) (ERR error) {}` d.After = `func f(in int, out *int) (err error) {}` }
Habituellement, il vous suffit de remplir 3 champs:
Summary
- une description de l'action de validation en une phrase.Before
- code avant correction.After
- code après correction (ne doit pas provoquer d'avertissement).
Génération de documentationLa régénération de la documentation n'est pas une condition préalable pour un nouveau linter, peut-être que dans un proche avenir, cette étape sera entièrement automatisée. Mais si vous voulez toujours vérifier à quoi ressemblera le fichier de démarque de sortie, utilisez la commande make docs
. Le fichier docs/overview.md
sera mis à jour.
Enregistrer un nouveau linter et exécuter des tests
La touche finale enregistre un nouveau linter:
addChecker
attend un pointeur sur la valeur zéro du nouveau linter. Vient ensuite l'argument variadique, vous permettant de passer zéro ou plusieurs attributs qui décrivent les propriétés de l'implémentation de la règle.
attrSyntaxOnly
est un marqueur facultatif pour les linters qui n'utilisent pas les informations de type dans leur implémentation, ce qui vous permet de les exécuter sans effectuer de vérification de type. golangci-lint
marque ces linters avec l'indicateur «rapide» (car ils fonctionnent beaucoup plus rapidement).
attrExperimental
est un attribut attribué à toutes les nouvelles implémentations. La suppression de cet attribut n'est possible qu'après stabilisation de la vérification implémentée.
Maintenant que le nouveau linter est enregistré via addChecker, vous pouvez exécuter les tests:
Fusion optimiste (presque)
Lors de l'examen des demandes de tirage, nous essayons d'adhérer à la stratégie de fusion optimiste . Cela s'exprime principalement par l'acceptation des RP à l'égard desquels l'examinateur peut avoir des allégations, en particulier purement subjectives. Immédiatement après l'injection d'un tel patch, un PR peut suivre de l'examinateur, qui corrige ces lacunes, l'auteur du patch original est ajouté à CC (copie).
Nous avons également deux marqueurs de linter qui peuvent être utilisés pour éviter les drapeaux rouges en l'absence de consensus complet:
Experimental
: une mise en œuvre peut avoir une grande quantité de faux positifs, être inefficace (la source du problème est identifiée), ou «tomber» dans certaines situations. Vous pouvez infuser une telle implémentation si vous la marquez avec l'attribut attrExperimental
. Parfois, à l'aide d'expérimental, ces vérifications sont indiquées qui n'ont pas réussi à trouver un bon nom à partir du premier commit.VeryOpinionated
: si le VeryOpinionated
peut avoir à la fois des défenseurs et des ennemis, il vaut la peine de le marquer avec l'attribut attrVeryOpinionated
. De cette façon, nous pouvons éviter de rejeter des idées sur le style de code qui peuvent ne pas correspondre au goût de certains gophers.
Experimental
est une propriété d'implémentation potentiellement temporaire et réparable. VeryOpinionated
est une propriété de règle plus fondamentale indépendante de l'implémentation.
Il est recommandé de créer un ticket [checker-request]
sur github avant de soumettre l'implémentation, mais si vous avez déjà soumis une pull request, vous pouvez ouvrir le problème correspondant pour vous.
Pour plus de détails sur le processus de développement, voir CONTRIBUTING.md .
Les règles de base sont répertoriées dans la section des règles principales .
Mots de séparation
Vous pouvez participer au projet non seulement en ajoutant un nouveau linter.
Il existe de nombreuses autres façons:
- Essayez-le sur vos projets ou sur des projets open source importants / connus et signalez les faux positifs, les faux négatifs et autres défauts. Nous vous serions reconnaissants si vous ajoutez également une note sur le problème trouvé / résolu sur la page des trophées .
- Suggérer des idées pour de nouvelles inspections. Il suffit de créer un problème sur notre tracker.
- Ajoutez des tests pour les linters existants.
go-critic
critique votre code Go avec les voix de tous les programmeurs impliqués dans son développement. Tout le monde peut donc critiquer - rejoignez-nous!
