Cargando…

Esto está tardando más de lo esperado.

Estándares de código

Blade

Estos son los estándares que aplica el analizador. Cada regla dice qué revisa y por qué existe.

Todos los estándares

Marcado legible

Blade y HTML deben escribirse en varias líneas y con sangría. No generes marcado minificado: varios elementos colapsados en una sola línea, o una vista o componente completo escrito como una única línea larga.

  • Un elemento con hijos se abre y se cierra en su propia línea, con los hijos sangrados un nivel.
  • Los elementos hermanos van en líneas separadas.
  • Las directivas de Blade como @if, @foreach y @can se abren y se cierran en sus propias líneas, con el cuerpo sangrado.
  • Una lista de atributos larga — aproximadamente tres o más atributos, o una lista larga de clases de Tailwind — puede dividirse con un atributo por línea.

Un elemento sin hijos con una lista corta de atributos puede permanecer en una sola línea, como <x-input wire:model="object.name" :label="__('Name')" />. El contenido corto en línea se queda donde corresponde: no separes <strong>, <a> o {{ }} del párrafo o la etiqueta que los contiene solo para cumplir esta regla.
Esta regla rige el marcado que se escribe o se genera. No reformatees marcado existente que no se haya tocado solo para cumplirla. Las plantillas bajo resources/views/vendor/, la salida de compilación y los assets minificados quedan fuera del alcance.

{{-- Wrong --}}
<div class="flex flex-col gap-4"><x-input wire:model="object.name" :label="__('Name')" /><x-button type="submit">{{ __('Save') }}</x-button></div>

{{-- Correct --}}
<div class="flex flex-col gap-4">
    <x-input wire:model="object.name" :label="__('Name')" />
    <x-button type="submit">{{ __('Save') }}</x-button>
</div>

Preferir componentes de formulario del proyecto

Usa componentes de formulario del proyecto como <x-input>, <x-select>, <x-textarea>, <x-checkbox>, <x-radio> y otros componentes de formulario internos existentes o de WireUI en lugar de controles de formulario HTML sin procesar en los formularios de la aplicación.
Evita usar <input>, <select>, <option>, <textarea> y controles similares sin procesar cuando exista un componente equivalente del proyecto. Los componentes del proyecto proporcionan estilo coherente, etiquetas, errores, accesibilidad, comportamiento de Livewire y presentación de validación.
Para los campos de fecha, usa el componente del proyecto <x-date-input> en lugar de inputs de fecha HTML sin procesar o inputs genéricos con type="date". El componente de fecha gestiona de forma consistente el comportamiento de zona horaria del proyecto.
Se permiten controles HTML sin procesar para inputs ocultos, marcado requerido por proveedores, pequeños controles nativos del navegador sin un componente equivalente en el proyecto, o casos en los que el componente existente no pueda admitir el comportamiento requerido. Al usar un control sin procesar, el motivo debe quedar claro por el contexto cercano.

{{-- Wrong --}}
<select wire:model="user.role">
    <option value="admin">{{ __('Admin') }}</option>
</select>

{{-- Correct --}}
<x-select wire:model="user.role" :options="$roleOptions" option-key-value />

{{-- Wrong --}}
<x-input type="date" wire:model="object.starts_at" />

{{-- Correct --}}
<x-date-input wire:model="object.starts_at" />

Formato de opciones de x-select

Para las opciones locales de x-select, usa option-key-value con arrays en :options.
Construye ese array de clave/valor en el componente Livewire o en el view component que posee los datos, y pásalo a la plantilla listo para renderizar. No reformatees las opciones dentro del archivo Blade.

// Correct — the component shapes the options
public function getSelectOptions(): Collection
{
    return $this->getOptions()->mapWithKeys(function ($option) {
        return [$option => $option];
    });
}

Para selects asíncronos que usan async-data o :async-data, se permiten option-label y option-value porque la respuesta de la API define los campos de etiqueta/valor.

{{-- Correct --}}
<x-select wire:model="currency" :options="$currencyOptions" option-key-value />
<x-select :async-data="route('api.products.index')" option-label="name" option-value="id" wire:model.live="product" />

Envío de formularios en Livewire

Cuando una UI de Blade o Livewire contiene campos que se envían mediante una acción principal, envuelve esos campos en un <form> y vincula la acción de Livewire en el formulario con wire:submit o con la directiva de envío establecida en el proyecto. La acción principal debe ser un botón de envío para que, al pulsar Enter desde un campo, se envíe el formulario.
No te apoyes solo en wire:click en el botón principal de guardar/buscar/enviar para un formulario real, porque eso rompe el envío normal con teclado. Las acciones secundarias dentro del formulario, como cancelar, cerrar, añadir fila, eliminar fila o alternancias locales, deben usar type="button" cuando no deban enviar el formulario.
Esta regla no se aplica a botones aislados, enlaces, pestañas, toggles de dropdown, paginación, filtros instantáneos que se aplican mediante actualizaciones del modelo en vivo sin una acción de envío explícita, ni a controles de UI que no estén enviando un conjunto de campos.

{{-- Wrong --}}
<div>
    <x-input wire:model="object.name" :label="__('Name')" />
    <x-button wire:click="save">{{ __('Save') }}</x-button>
</div>

{{-- Correct --}}
<form wire:submit="save">
    <x-input wire:model="object.name" :label="__('Name')" />
    <x-button type="submit">{{ __('Save') }}</x-button>
    <x-button type="button" wire:click="closeModal">{{ __('Cancel') }}</x-button>
</form>

Comentarios del usuario y estados de error

La UI interactiva de Blade y Livewire debe ofrecer comentarios comprensibles cuando una acción no puede completarse o una entrada no es válida.

  • Los formularios deben mostrar errores de validación cerca de los campos correspondientes usando los componentes de formulario del proyecto o el patrón de visualización de errores ya establecido en el proyecto.
  • Los botones o acciones que pueden fallar deben mostrar un mensaje claro para el usuario mediante el patrón de feedback existente, como errores de validación, notificaciones/diálogos de WireUI, errores inline o un componente de alerta existente.
  • Los elementos que visualmente parecen botones de acción deben realizar una acción, navegar, abrir/cerrar la UI, enviar, despachar un evento o comunicar claramente por qué no están disponibles. Si un elemento es solo informativo o decorativo, no lo estilices como un botón accionable; usa texto, badge, label, tooltip, estado deshabilitado u otro patrón no orientado a la acción.
  • Informa un elemento con apariencia de botón como un error de UX cuando no tenga comportamiento de clic/navegación/envío ni una semántica clara de no disponible/deshabilitado.
  • No dejes una acción del usuario sin un resultado visible cuando la operación esté bloqueada, sea rechazada, no sea válida o falle del lado del servidor.
  • El texto del error debe explicar qué ocurrió o qué necesita cambiar el usuario. Los mensajes genéricos como Error, Invalid o Something went wrong no son suficientes cuando el código conoce la razón específica.
  • Es aceptable que los controles de UI puramente locales, como tabs, toggles de dropdown, botones de cerrar modal, filtros y paginación, eviten mensajes explícitos de éxito/error cuando no estén realizando una operación riesgosa o persistente.

Autorización en Componentes Livewire

Toda vista Blade que lea o modifique recursos protegidos debe aplicar autorización cuando haya acciones protegidas o datos protegidos presentes.

  • Para botones de acciones protegidas que modifican estado y existen en la vista (delete, migrate, edit), prefiere las directivas @can / @cannot al agregar o cambiar la visibilidad por autorización
  • Los wrappers de autorización equivalentes ya existentes en el proyecto son aceptables cuando ya delegan en autorización respaldada por policy; no los reescribas solo por estilo
  • Nunca dupliques la lógica de la policy dentro de la vista. Si la policy ya comprueba una condición (p. ej. hasSubscribers), no la vuelvas a comprobar en Blade — confía en la policy
  • El wrapper @can controla solo la visibilidad; el componente Livewire igualmente debe tener su guard autoritativo del lado del servidor a nivel de componente, normalmente en mount() como se define en el estándar de Livewire
  • No exijas @can para controles de UI que no modifican estado, como closeModal, cancel, cambios de pestaña, filtros, paginación u otras acciones que solo afectan al estado local de la UI.
  • No exijas un wrapper @can adicional dentro de una vista dedicada de modal/componente Livewire cuando el propio componente ya autoriza el recurso protegido en mount() o a nivel de componente. La regla de Blade protege la visibilidad en vistas padre/de lista; no debe duplicar la regla de autorización a nivel de componente para cada botón dentro de un componente ya autorizado.
{{-- Correct — policy handles all conditions internally --}}
@can('delete', $billingPlan)
    <x-button wire:click="deletePlan" ... />
@endcan

{{-- Incorrect — duplicates logic already inside the policy --}}
@can('delete', $billingPlan)
    @if (! $billingPlan->hasSubscribers)
        <x-button wire:click="deletePlan" ... />
    @endif
@endcan

Los modales deben usar wire-elements/modal

No uses el marcado de modal de WireUI en Blade. Reporta esta regla solo cuando el Blade revisado contenga <x-modal>.
Usa wire-elements/modal (LivewireUI\Modal\ModalComponent) para las pantallas modales. El marcado ordinario de Livewire como wire:model, entradas de formulario, botones, closeModal, save o confirm no es evidencia para esta regla.

{{-- Wrong --}}
<x-modal wire:model="showModal">
    ...
</x-modal>

{{-- Correct --}}
<button wire:click="$dispatch('openModal', { component: 'posts.create-post' })">
    New Post
</button>

Las vistas no transforman datos

Una vista renderiza lo que le dieron. No consulta, ni filtra, ni ordena, ni agrupa, ni reorganiza.
El trabajo hecho dentro de una plantilla corre en cada render, es invisible para los tests del componente y no lo puede reutilizar nada más. Muévelo al componente —una propiedad #[Computed] o un método que devuelva los datos ya preparados— y deja que la plantilla lea el resultado.

{{-- Correcto --}}
@foreach ($this->activeOrders as $order)

{{-- Incorrecto --}}
@php $orders = Order::where('active', true)->get(); @endphp
@foreach ($orders->filter(fn ($o) => $o->total > 0)->sortBy('name') as $order)

Leer un valor, llamar a un método que devuelve datos preparados y una cadena que termina en un string —una etiqueta, una fecha con formato, una lista unida— son renderizado, no transformación. Lo que esta regla reporta es una cadena que devuelve una colección o que ejecuta una consulta.

Los controles solo con icono tienen un nombre accesible

Un botón o enlace cuyo único contenido es un icono debe tener un nombre accesible: un aria-label, aria-labelledby o title en el control o en el icono que contiene, un <title> dentro del SVG, o texto traducido dentro de un elemento sr-only. Sin ninguno de ellos, un lector de pantalla anuncia el control como "botón" o "enlace" sin nada que indique su función, y la persona que lo usa no puede distinguir eliminar de editar.

  • Un <button> o <a> nativo que muestre solo un SVG, un componente de icono, un <i> de fuente de iconos o una directiva @svg o @icon, sin ningún nombre visible en el mismo archivo, es un error.
  • Un <x-button> o <x-mini-button> con solo un icon y sin etiqueta es una advertencia: si el componente genera un nombre a partir de su icono lo decide su propio archivo.
  • Un <button> o <a> nativo que propaga {{ $attributes }} toma su nombre de quien lo renderiza, por lo que es una advertencia que pide que cada llamador pase uno.

El nombre es texto como cualquier otro y también pasa por la traducción.

{{-- Wrong --}}
<button wire:click="delete({{ $item->id }})">
    <x-icon name="trash" class="h-4 w-4" />
</button>

{{-- Correct --}}
<button wire:click="delete({{ $item->id }})" aria-label="{{ __('Delete') }}">
    <x-icon name="trash" class="h-4 w-4" />
</button>

El texto visible pasa por la traducción

El texto que una persona lee en una vista, ya sea entre etiquetas o en un atributo que una persona lee o escucha, como alt, title, placeholder, aria-label, label, description, hint o tooltip, se muestra con __(), y su entrada se añade a cada archivo de idioma tal como describe la regla de PHP Idioma y texto. Esa regla informa del contenido en un idioma distinto del inglés que se muestra a una persona. Esta es la preocupación de traducción aparte que dicha regla deja fuera: informa del texto escrito directamente en una vista, incluido el inglés, por lo que ambas pueden informar del mismo contenido en otro idioma. El texto escrito directamente en el marcado se entrega en un único idioma, sin importar el que haya elegido el visitante, y nadie lo nota hasta que una persona que usa otro idioma abre la página.
Se trata de una advertencia, porque una cadena simple a veces es un nombre que permanece igual en todos los idiomas. No se informa de números, símbolos, siglas, direcciones, nombres en clave, marcado escapado, contenido que procede de una variable, ni del texto dentro de scripts, estilos, ejemplos de código y glifos de iconos. El texto formado solo por palabras con mayúscula inicial solo se informa cuando una de ellas es una palabra corriente de la interfaz, como Dashboard o Close, ya que de lo contrario podría tratarse de una marca o del nombre de una persona.

{{-- Wrong --}}
<h1>Dashboard</h1>
<img src="{{ $logo }}" alt="Company logo">

{{-- Correct --}}
<h1>{{ __('Dashboard') }}</h1>
<img src="{{ $logo }}" alt="{{ __('Company logo') }}">

Los plurales vienen de los archivos de traducción

Una palabra que cambia según la cantidad se elige con trans_choice() y una entrada plural en los archivos de idioma, nunca comparando la cantidad en la vista ni con Str::plural(). Un ternario con === 1 supone que todos los idiomas tienen exactamente un singular y un plural, lo cual es falso para idiomas con tres o más formas, y Str::plural() aplica las terminaciones de un idioma —el inglés salvo que se configure otra cosa— a cualquier palabra que reciba, de modo que un canción en español resulta en cancións.
Esto es una advertencia: una comparación contra un valor que elige entre dos textos no relacionados no es un plural y se deja como está.

{{-- Wrong --}}
{{ $count }} {{ $count === 1 ? __('item') : __('items') }}
{{ $count }} {{ Str::plural('item', $count) }}

{{-- Correct --}}
{{ trans_choice('{0} No items|{1} :count item|[2,*] :count items', $count) }}

La cadena es la clave de traducción: añádela a todos los archivos de idioma con las formas que necesite cada idioma.

Weblabor Logo Desarrollado por Weblabor