Go lintpack: administrador de linter composable


lintpack es una utilidad para construir linter (analizadores estáticos), que se escriben utilizando la API proporcionada. En base a esto, el analizador estático crítico , familiar para algunos, ahora se está reescribiendo.


Hoy lintpack con más detalle qué lintpack desde el punto de vista del usuario.


Al principio fue crítico ...


Go-Critique comenzó como un proyecto piloto que era una caja de arena para la creación de prototipos de casi cualquier idea de análisis estático para Go.


Una sorpresa agradable fue que algunas personas enviaron implementaciones de detectores de varios problemas en el código. Todo estaba bajo control hasta que comenzó a acumularse una deuda técnica, que prácticamente no había nadie para eliminar. Entraron personas, agregaron verificación y luego desaparecieron. ¿Quién tiene que corregir los errores y modificar la implementación?


Un evento significativo fue la propuesta de agregar controles que requieren configuración adicional, es decir, aquellos que dependen de los arreglos locales para el proyecto. Un ejemplo es revelar la presencia de un encabezado de copyright en un archivo (encabezado de licencia) usando una plantilla especial o prohibiendo la importación de algunos paquetes con la propuesta de una alternativa dada.


Otra dificultad fue la extensibilidad. No todos se sienten cómodos enviando su código al repositorio de otra persona. Algunos querían conectar dinámicamente sus comprobaciones para no tener que modificar los códigos fuente go-critic .


En resumen, estos son los problemas que se interpusieron en el camino del desarrollo go-critic :


  • Una carga de complejidad. Demasiado soporte, la presencia de un código sin propietario.
  • Baja calidad media. experimental significaba "casi listo para usar" y "mejor no correr en absoluto".
  • A veces es difícil decidir si se incluye la verificación en go-critic y rechazarla contradice la filosofía de diseño original.
  • Diferentes personas vieron go-critic diferente. La mayoría quería tenerlo como un linter CI, que viene con gometalinter .

Para limitar de alguna manera el número de discrepancias e interpretaciones inconsistentes del proyecto, se escribió un manifiesto .


Si desea un contexto histórico adicional e incluso más ideas sobre la categorización de los analizadores estáticos, puede escuchar la grabación de GoCritic, un nuevo analizador estático para Go . En ese momento, lintpack aún no existía, pero algunas de las ideas nacieron ese día, después del informe.

Pero, ¿y si no necesitáramos almacenar todos los cheques en un repositorio?


Conoce - lintpack




go-critic consta de dos componentes principales:


  1. Implementación de los propios controles.
  2. Un programa que descarga paquetes validados por Go y ejecuta validaciones en ellos.

Nuestro objetivo: poder almacenar cheques para el linter en diferentes repositorios y recogerlos cuando sea necesario.


lintpack hace exactamente eso. Define funciones que le permiten describir sus comprobaciones de tal manera que luego pueda ejecutarlas a través del linter generado.


Los paquetes que se implementan utilizando lintpack como marco se denominarán lintpack compatibles con lintpack o compatibles con lintpack .

Si go-critic lintpack se implementara sobre la base de lintpack , todas las comprobaciones podrían dividirse en varios repositorios. Una de las opciones de separación puede ser la siguiente:


  1. El conjunto principal donde se obtienen todas las comprobaciones estables y compatibles.
  2. repositorio contrib donde se encuentra el código que es demasiado experimental o carece de mantenedor.
  3. Verificaciones personalizables para un proyecto específico.

El primer punto es de particular importancia en relación con la integración de go-critical en golangci-lint .


Si te mantienes en el nivel go-critic , entonces para los usuarios casi nada ha cambiado. lintpack crea un linter casi idéntico, mientras que golangci-lint encapsula todos los detalles de implementación diferentes.


Pero algo ha cambiado. Si se crean nuevas lintpack sobre la base de lintpack , tendrá una selección más rica de diagnósticos listos para generar un linter. Imagine por un momento que esto es así, y que hay más de 10 conjuntos de controles diferentes en el mundo.


Inicio rápido



Para comenzar, debe instalar lintpack :


 # lintpack    `$(go env GOPATH)/bin`. go get -v github.com/go-lintpack/lintpack/... 

Cree un linter usando el paquete de prueba de lintpack :


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

El conjunto incluye panicNil , que encuentra panic(nil) en el código y panicNil un reemplazo con algo distinguible, porque de lo contrario, recover() no podrá decir si se llamó al panic con argumento nil , o si no hubo pánico en absoluto.


Ejemplo con pánico (nulo)


El siguiente código intenta describir el valor obtenido de recover() :


 r := recover() fmt.Printf("%T, %v\n", r, r) 

El resultado será idéntico para el panic(nil) y para un programa que no panic(nil) .


Un ejemplo de ejecución del comportamiento descrito .




Puede iniciar la interfaz en archivos separados, con argumentos de tipo ./... o paquetes (por su ruta de importación).


 ./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 

Por defecto, esta verificación también responde al panic(interface{}(nil)) . Para anular este comportamiento, establezca skipNilEfaceLit en true . Puede hacer esto a través de la línea de comando:


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

uso para cmd / lintpack y linter generado


Tanto lintpack como el linter generado utilizan el primer argumento para seleccionar un subcomando. La lista de subcomandos disponibles y ejemplos de su lanzamiento se puede obtener llamando a la utilidad sin argumentos.


 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 lintpack version $ lintpack version 

Supongamos que gocritic el linter creado por el nombre 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 linter version $ linter version doc - get installed checkers documentation $ linter doc -help $ linter doc $ linter doc checkerName 

El indicador -help está disponible para algunos subcomandos, que proporcionan información adicional (corté algunas líneas demasiado anchas):


 ./gocritic check -help #     . 



Documentación de cheques instalados


La respuesta a la pregunta "¿cómo averiguar sobre el parámetro skipNilEfaceLit?" - lea el manual de lujo (RTFM)!


Toda la documentación sobre los cheques instalados está dentro de mylinter . Esta documentación está disponible a través del subcomando 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) 

Al igual que el soporte para plantillas en go list -f , puede pasar una línea de plantilla que es responsable del formato de salida de la documentación, que puede ser útil al escribir documentos de rebajas.


¿Dónde buscar controles para la instalación?


Para simplificar la búsqueda de conjuntos de lintpack útiles, hay una lista centralizada de paquetes compatibles con lintpack : https://go-lintpack.imtqy.com/ .


Aquí hay algunos de la lista:



Esta lista se actualiza periódicamente y está abierta a solicitudes de adición. Cualquiera de estos paquetes se puede usar para crear un linter.


El siguiente comando crea un linter que contiene todas las comprobaciones de la lista anterior:


 #   ,      #   Go . go get -v github.com/go-critic/go-critic/checkers go get -v github.com/go-critic/checkers-contrib # build   . lintpack build \ github.com/go-critic/go-critic/checkers \ github.com/go-critic/checkers-contrib 

lintpack build incluye todas las comprobaciones en la etapa de compilación, el linter resultante se puede colocar en un entorno donde no hay códigos fuente para la implementación de diagnósticos instalados, todo es como siempre con el enlace estático.


Anexo de paquete dinámico


Además del ensamblaje estático, es posible cargar complementos que proporcionan comprobaciones adicionales.


La peculiaridad es que la implementación del verificador no sabe si se usará para la compilación estática o si se cargará como un complemento. No se requieren cambios en el código.


Supongamos que queremos agregar panicNil al linter, pero no podemos reconstruirlo a partir de todas las fuentes que se usaron durante la primera compilación.


  1. Crear linterPlugin.go :

 package main //         , //    import'. import ( _ "github.com/go-lintpack/lintpack/checkers" ) 

  1. Construye una biblioteca dinámica:

 go build -buildmode=plugin -o linterPlugin.so linterPlugin.go 

  1. Ejecute el linter con el parámetro -pluginPath :

 ./linter check -pluginPath=linterPlugin.so bytes 

Advertencia: El soporte para módulos dinámicos se implementa a través de un paquete de complemento que no funciona en Windows.

El indicador -verbose puede ayudarlo a determinar qué verificación está activada o desactivada y, lo más importante, mostrará qué filtro ha inhabilitado la verificación.


Ejemplo con -verbose


Tenga en cuenta que panicNil muestra en la lista de comprobaciones incluidas. Si eliminamos el argumento -pluginPath, dejará de ser cierto.


 ./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 # ...   . 



Comparación con gometalinter y golangci-lint


Para evitar confusiones, vale la pena describir las principales diferencias entre los proyectos.


gometalinter y golangci-lint integran principalmente otras linters , a menudo implementadas de manera muy diferente, que proporcionan un acceso conveniente a ellas. Se dirigen a usuarios finales que utilizarán analizadores estáticos.


lintpack simplifica la creación de nuevas linters, proporciona un marco que hace que diferentes paquetes, implementados sobre la base, sean compatibles dentro del mismo archivo ejecutable. Estas comprobaciones (para golangci-lint) o el archivo ejecutable (para gometalinter) se pueden incrustar en los meta-linters mencionados anteriormente.


Supongamos que una de las lintpack compatibles con lintpack es parte de golangci-lint . Si hay algún problema relacionado con su usabilidad, esto puede ser responsabilidad de golangci-lint , pero si es un error al implementar la verificación en sí, entonces este es el problema de los autores de la verificación, lintpack ecosistema.


En otras palabras, estos proyectos resuelven varios problemas.


¿Qué pasa con go-critico?


El proceso de portar go-critic lintpack a lintpack ya se ha completado; los verificadores se pueden encontrar en el repositorio go-critic lintpack / checkers .


 #  go-critic : go get -v github.com/go-critic/go-critic/... #  go-critic : lintpack -o gocritic github.com/go-critic/go-critic/checkers 

Tiene poco sentido usar go-critic golangci-lint fuera de golangci-lint , pero lintpack puede permitirle instalar esas comprobaciones que no están incluidas en el conjunto go-critic . Por ejemplo, estos pueden ser diagnósticos escritos por usted.


Para continuar


Aprenderá a crear sus propios lintpack compatibles con lintpack en el siguiente artículo.


Allí analizaremos qué ventajas obtiene al implementar su linterpack basado en lintpack en comparación con la implementación desde cero.


Espero que tenga apetito por nuevos cheques para Go. Avíseme cuánto análisis estático se vuelve demasiado, resolveremos rápidamente este problema juntos.

Source: https://habr.com/ru/post/es430196/


All Articles