Implementando una regla de lint de Redocly CLI
Convierte una verificación escrita en lenguaje natural ("cada esquema de arreglo debe declarar items") en una regla de lint de Redocly que funcione y esté probada.
La escalera. Redocly ofrece tres formas de realizar una verificación. La primera es la más barata de mantener.
- Regla integrada — una de las reglas integradas realiza la verificación.
- Regla configurable — una o más entradas
rule/*enredocly.yamlrealizan la verificación con aserciones. - Plugin personalizado — código JavaScript que implementa tu propia regla con el patrón de visitante. Usa este escalón al final.
Realiza las verificaciones de una en una. Comienza cada verificación en el escalón 1. Baja un escalón solo cuando el escalón actual no pueda realizar la verificación. Toma toda la sintaxis de la documentación oficial o del recetario. Confía en la documentación, no en tu memoria. Usa sintaxis moderna, como ESM en lugar de CommonJS. Si el proyecto tiene su propio estilo, sigue el proyecto.
1. Triage
Para cada verificación, anota tres cosas: el nodo que examina, qué debe ser verdadero, y cómo se ve una violación. Haz el triaje de cada verificación por separado. Diferentes verificaciones pueden terminar en diferentes escalones.
Obtén los nombres de los nodos del comando inspect-node-types, no de la memoria.
El árbol de tipos es diferente en cada especificación y cada versión.
Los nombres que imprime son el subject.type de una regla configurable y las claves de visitante de un plugin.
| para aprender | ejecutar |
|---|---|
| el tipo en una ubicación | --pointer='#/paths/~1orders' |
| cada nodo, o cada nodo de un tipo | sin bandera, o --type=Schema |
| qué tipos usa la descripción | --summary |
| dónde puede estar un tipo | --type=<Tipo> --parents |
| los ancestros de un nodo | --pointer=<puntero> --parents |
Las dos últimas filas dan a una regla su alcance.
Cuando un tipo se encuentra debajo de más de un padre, la regla necesita una puerta where.
Sin esa puerta, la regla también reporta las ramas que no deseas.
El comando muestra la descripción frente a ti. No muestra todo lo que la especificación permite.
Cuando el comando no imprime una rama, esa rama no está comprobada. No está ausente.
El comando no acorta el paso 3. Te da nombres de tipos. No te dice si tus aserciones expresan la verificación.
Sube la escalera para cada verificación:
- Busca en la lista de reglas integradas el concepto.
Una barra inclinada final en una ruta da
no-path-trailing-slash. Cuando la configuración ya extiende un conjunto de reglas que activa la regla integrada, díselo al usuario. No agregues la regla de nuevo. Usa la regla integrada cuando su descripción coincida con la verificación, incluso si reporta más que la regla que reemplazas. Una regla integrada suele ser una versión mejorada de la misma idea, por lo que sus hallazgos adicionales son la verificación hecha correctamente. Cuando escribes el comportamiento más específico a mano, intercambias una regla mantenida por código que debes mantener. Baja un escalón solo cuando la regla integrada verifique algo diferente. Reporta qué regla integrada tomaste y qué añade. - Asegúrate de que la regla integrada soporte las versiones de especificación que el proyecto lint.
Algunas reglas integradas solo soportan una versión.
no-invalid-media-type-examplessoporta OpenAPI 3.x, por lo que deja una verificación de Swagger 2.0 sin aplicar. Redocly imprimeUnused rules found in redocly.yamlcuando una regla habilitada no puede aplicarse a un documento. Corrige esa línea. No es ruido. - Prueba aserciones y una puerta
where. Consulta "Reglas configurables" más abajo. Una verificación puede necesitar varias entradasrule/*, una para cada caso o ubicación. Eso sigue siendo mejor que un plugin. - Escribe un plugin solo cuando las reglas configurables no puedan expresar la verificación, incluso en combinación. Primero trabaja con los tres diales a continuación. Un plugin puede hacer cualquier verificación, pero es código. Añade complejidad, mantenimiento y sus propios errores.
- Dile al usuario que abra un issue solo cuando ninguna regla configurable pueda cerrar una brecha real.
El triaje está completo cuando cada verificación tiene un escalón. Da una solución para cada verificación: el escalón más alto que funcione. Reporta solo aquello sobre lo que el usuario debe actuar: un desajuste entre una descripción y una implementación, una regla no comprobada, o una decisión que podría ir en otra dirección. Reporta en el chat. Nunca escribas un archivo de resumen. La configuración registra el resto, así que no narres el escalón de cada verificación.
Migrar desde otro linter
- Migra el conjunto de reglas efectivo, no solo las reglas escritas en el archivo.
Un
extends, o una base heredada, activa más reglas con sus severidades predeterminadas. Cada una de esas reglas también es una verificación. Resuelve esa base y cuenta las reglas antes de empezar. Así sabrás el tamaño real del trabajo. - Toma cada verificación de dos lugares: la descripción de la regla y su implementación. Nunca la tomes de tu memoria del nombre de la regla. Cuando los dos discrepen, usa la descripción. La descripción dice lo que el autor quería, y la implementación puede tener un error. Dile al usuario sobre la diferencia.
- Omite una regla con severidad
off. No la migres, ni siquiera como entrada deshabilitada. Cuando las reglas habilitadas estén listas, lista cada regla omitida en una línea. Entonces el usuario puede decidir. - Para Spectral, lee primero la tabla de mapeo en la guía de migración. Muchas reglas principales de Spectral se asignan a una regla integrada. Una celda vacía significa que bajas en la escalera.
- Cuando copies una función de la fuente, también copias sus errores en un host más estricto.
Redocly detiene todo el documento cuando una regla lanza una excepción, pero el linter fuente puede detener solo una regla.
Una coincidencia sin protección, como
v.match(/…/)[0]en un valor que puede no coincidir, elimina todos los hallazgos en ese archivo. Añade una protección al código que copies. Luego haz lint de un documento que tenga la forma difícil.
2. Configurar
Escribe en el archivo redocly.yaml del usuario. Usa ese nombre exacto, sin punto inicial.
Cuando el archivo exista, léelo primero y conserva el extends y las reglas existentes.
Establece cada regla en error, a menos que el usuario pida otro nivel.
Cuando migres, conserva la severidad de origen y elige el nivel de Redocly más cercano.
Solo existen error, que falla la ejecución, warn y off, por lo que info y hint se convierten en warn.
Referencia cada archivo que crees desde la configuración, y ejercítalo en la ejecución de lint. No dejes archivos muertos.
Una configuración sin extends no hereda nada — ni recommended, ni siquiera struct.
La base predeterminada se aplica solo cuando no existe ningún archivo de configuración.
Por lo tanto, un redocly.yaml ejecuta exactamente las reglas que nombra.
El escalón de regla integrada existe solo cuando extends nombra un conjunto de reglas, o cuando la configuración lista cada regla integrada por sí misma.
Con ninguna de las dos, un documento que infringe la especificación tres veces pasa el lint sin problemas.
Conecta cada regla.
Un plugin en plugins: no hace nada cuando sus reglas están ausentes de rules:, y Redocly no da ninguna advertencia.
Después de escribir la configuración, lista los IDs de regla que deben estar activos.
Confirma que cada ID esté en rules: o en un bloque con alcance de especificación.
Cuando todas las reglas estén incluidas, compara tu lista con los conjuntos de reglas predefinidos: minimal, recommended, recommended-strict y spec.
- Cuando tus reglas se acerquen a un conjunto de reglas, extiéndelo. Conserva solo las diferencias como anulaciones: otras severidades, reglas para desactivar, y verificaciones que el conjunto de reglas no contiene.
- Un conjunto de reglas también activa reglas que el usuario no solicitó.
Lee lo que añade y anula cada regla que contradiga o repita una regla que escribiste.
Un conjunto de reglas que activa una verificación que la fuente establece en
offes un error a corregir, no un beneficio. - Cuando las anulaciones sean tantas como las reglas, o más, conserva la lista simple y sin
extends.
3. Verificar
Una regla no está comprobada hasta que una ejecución en vivo la haga reportar.
Comprueba cada regla con npx redocly lint <archivo-api> en dos direcciones:
- un fixture violador: la regla reporta, en la ubicación correcta;
- un fixture conforme: la ejecución permanece silenciosa.
Un falso positivo es el fallo común, por lo que una regla que solo pasó el fixture violador no está comprobada. Escribe el fixture violador para separar esta regla de la regla que la acompaña: la regla que reemplazas o su vecina en la configuración. Dos errores necesitan esa separación:
- Pon un valor falso en el fixture violador.
Una regla que debe rechazar un
descriptionvacío debe reportar endescription: ''. Cuando pones''solo en el fixture conforme, no pruebas nada, porque una aserción de presencia lo acepta. La regla entonces parece correcta y la brecha permanece. - Una regla integrada que reemplaza dos verificaciones fuente necesita un fixture que rompa solo la segunda verificación.
no-unsafe-markdownreporta<script>, pero nuncaeval(. Un fixture que contiene ambas da crédito a la regla integrada por una verificación que no realiza.
Para mantener la salida limpia, pasa una configuración temporal pequeña con --config.
Elimina esa configuración y los fixtures cuando termines.
Cada fragmento que entregues debe ser correcto tal como está escrito: analiza como YAML y se comporta como afirmas.
Has terminado cuando cada verificación nombre un fixture violador que reportó y un fixture conforme que permaneció silencioso.
Nombra esos fixtures en tu informe.
Un conteo no es evidencia.
Una ejecución limpia en los propios archivos del proyecto tampoco es evidencia: solo muestra que nada reporta allí, y eso es cierto tanto para una regla correcta como para una regla muerta.
Cuando verifiques las reglas riesgosas y asumas las demás, la entrega no está comprobada. Dilo, y nombra cada regla no comprobada.
Un fixture conforme solo prueba las formas que contiene.
Una versión de especificación que no probaste, o una ubicación que --parents mostró y no ejercitaste, permanece sin probar. Repórtalo.
Cuando una ejecución en vivo sea imposible, marca la regla como no verificada. No afirmes que funciona.
También has terminado cuando cada verificación solicitada esté en la configuración o en la lista omitida, y el proyecto contenga solo los archivos de configuración y plugin que creaste.
Reglas configurables
Documentación: reglas configurables. Ejemplos prácticos: recetario/reglas-configurables.
Una regla es una entrada bajo rules: con un nombre que comienza con rule/:
rules:
rule/mi-verificacion:
subject:
type: Schema # un tipo de nodo de la lista de tipos de la documentación
property: title # opcional
assertions:
defined: true
where: [] # puerta opcional
message: Texto de problema personalizado.
severity: error
rules simple se aplica a cada documento que el proyecto lint. Mantén las reglas allí por defecto.
Mueve una regla bajo una clave con alcance de especificación solo cuando la verificación necesite algo que exista en una versión de especificación, como nullable en OAS 3.0, o cuando el usuario lo solicite.
Las claves son oas2Rules, oas3_0Rules, oas3_1Rules, oas3_2Rules, async2Rules, async3Rules, arazzo1Rules y overlay1Rules.
Una regla dentro de una clave no se ejecuta para las otras versiones, por lo que una verificación que el proyecto necesita en dos versiones necesita una entrada que ambas versiones alcancen.
En su lugar, delimita una regla de plugin dentro del plugin, en su mapa rules.
Qué prueba una aserción
Dos hechos causan la mayoría de los falsos positivos y la mayoría de las omisiones silenciosas.
Un valor ausente pasa.
Casi todas las aserciones se cumplen cuando el valor que verifican está ausente.
const: array en property: type coincide con un esquema tipado array, y también con cada esquema sin type.
Por lo tanto, cuando una entrada where prueba un valor de propiedad, añade defined: true junto a la aserción de valor si la propiedad puede estar ausente.
Presencia no es valor.
defined, required, requireAny, disallowed y mutuallyExclusive prueban si existe una clave.
No dicen nada sobre el contenido, por lo que defined: true en description pasa para description: ''.
Afirma el valor en su lugar: nonEmpty: true rechaza una cadena vacía, y const, enum y pattern prueban el contenido.
Este es el defecto más frecuente en la práctica.
Se ve correcto en la revisión, y solo un fixture violador que contenga '' lo muestra.
Dos consecuencias de estos hechos:
const: <valor>es también la forma de decir "este campo debe ser falso", porque la aserción se cumple para ese valor y para un campo ausente.requireAnycuenta solo claves. "Al menos uno de estos campos, y no vacío" no tiene forma de aserción. Esa verificación es un caso real de plugin.
Qué inspecciona la aserción
property: nombre— el valor de ese campo en el nodo sujeto.property: [nombre, url, email]— cada campo listado por separado. Cada campo que falla se convierte en su propio problema con su propio puntero JSON, como#/info/contact/nombre.propertyausente — las claves del nodo sujeto. Por ejemplo,subject: { type: Paths }conassertions: { notPattern: /.+\/$/ }se ejecuta en las cadenas de ruta mismas.
Apunta al sujeto, luego ponle una puerta
Tres diales apuntan una regla. Resuélvelos en este orden.
1. Sujeto: el nodo que posee lo que afirmas.
- Una verificación sobre las claves que contiene un mapa apunta al tipo del mapa mismo.
Los mapas de respuestas, encabezados, propiedades de esquema y rutas son cada uno su propio tipo.
Por lo tanto, "este mapa debe contener esa clave" es un
requireden un sujeto. No busques en el padre. - Una verificación sobre un valor apunta al nodo que contiene el campo, más
property. type: anycon unpropertyverifica esa propiedad en todas partes donde aparezca.- Confirma el nombre del tipo con
inspect-node-types, en un documento de la versión de especificación a la que apunta la regla. Un tipo con nombre puede existir en una versión y no en otra.HeadersMapes un tipo en OpenAPI 3. En OpenAPI 2, el mapaheadersde la respuesta es anónimo y no tiene nombre de tipo, por lo que un sujetoHeadersMapbajooas2Rulesnunca reporta. Redocly advierte sobre un ID de regla no resuelto, pero no sobre unsubject.typeno resuelto, por lo que un tipo incorrecto te da una regla que nunca reporta.
...
[truncado para traducción]
2. Puerta: la condición que activa la regla.
Ninguna puerta significa que la regla se aplica a cada ocurrencia del tipo del sujeto. Una puerta con una entrada restringe el alcance.
where:
- subject:
type: PathItem
assertions:
defined: [get, post]
Esta puerta dice: "solo se aplica a sujetos cuyo padre es un PathItem que tiene get o post".
3. Mensaje y severidad.
El mensaje debe explicar el problema y, si es posible, cómo solucionarlo.
Plugins
Documentación: plugins.
Cuando las reglas configurables no sean suficientes, escribe un plugin.
Un plugin exporta un objeto con un mapa rules:
export default {
id: 'mi-plugin',
rules: {
'mi-verificacion': {
// implementación de la regla
}
}
};
Un plugin puede acceder al árbol de nodos completo y al contexto del linter, lo que permite verificaciones complejas.
Siempre prefiere reglas configurables sobre un plugin.
Resumen de la escalera
- Regla integrada — la más simple, sin configuración adicional.
- Regla configurable — aserciones en
redocly.yaml, sin código. - Plugin personalizado — código JavaScript, solo cuando sea estrictamente necesario.
Cada escalón es más costoso de mantener que el anterior. Sube la escalera de manera eficiente.