@skapxd/eslint-opinionated 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +295 -32
  2. package/dist/astro/index.js +0 -3
  3. package/dist/astro/index.js.map +1 -1
  4. package/dist/astro/index.mjs +2 -2
  5. package/dist/{chunk-QR35MHTW.mjs → chunk-4FQ7SFU4.mjs} +31 -33
  6. package/dist/chunk-4FQ7SFU4.mjs.map +1 -0
  7. package/dist/{chunk-C5JP4FQN.mjs → chunk-7OIMY5TI.mjs} +2 -2
  8. package/dist/{chunk-NBEAEKXG.mjs → chunk-LSLLVT64.mjs} +7 -14
  9. package/dist/chunk-LSLLVT64.mjs.map +1 -0
  10. package/dist/{chunk-YUKVSZZ4.mjs → chunk-X2DU5BAB.mjs} +148 -6
  11. package/dist/chunk-X2DU5BAB.mjs.map +1 -0
  12. package/dist/{chunk-TZ5XPZGL.mjs → chunk-YRWX3POD.mjs} +2 -2
  13. package/dist/index.js +184 -51
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.mjs +6 -6
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/nest/index.d.mts +9 -13
  18. package/dist/nest/index.d.ts +9 -13
  19. package/dist/nest/index.js +29 -36
  20. package/dist/nest/index.js.map +1 -1
  21. package/dist/nest/index.mjs +2 -2
  22. package/dist/next/index.js +0 -3
  23. package/dist/next/index.js.map +1 -1
  24. package/dist/next/index.mjs +2 -2
  25. package/dist/shared/index.d.mts +12 -14
  26. package/dist/shared/index.d.ts +12 -14
  27. package/dist/shared/index.js +178 -38
  28. package/dist/shared/index.js.map +1 -1
  29. package/dist/shared/index.mjs +2 -2
  30. package/package.json +1 -1
  31. package/dist/chunk-NBEAEKXG.mjs.map +0 -1
  32. package/dist/chunk-QR35MHTW.mjs.map +0 -1
  33. package/dist/chunk-YUKVSZZ4.mjs.map +0 -1
  34. /package/dist/{chunk-C5JP4FQN.mjs.map → chunk-7OIMY5TI.mjs.map} +0 -0
  35. /package/dist/{chunk-TZ5XPZGL.mjs.map → chunk-YRWX3POD.mjs.map} +0 -0
package/README.md CHANGED
@@ -98,11 +98,11 @@ la que defiende el axioma más fundamental (el orden es jerárquico).
98
98
 
99
99
  | # | Axioma | Reglas que lo ejecutan |
100
100
  | --- | --- | --- |
101
- | A1 | **Los estados imposibles son irrepresentables.** El tipo modela exactamente los estados válidos; lo inválido no compila. | `prefer-tagged-union-state`, `no-runtime-state-guard`, `requires-strict-tsconfig`, `@typescript-eslint/no-unnecessary-condition`, `no-explicit-any`, `consistent-type-definitions` |
102
- | A2 | **Ningún efecto es invisible al tipo.** Si una operación puede fallar, su firma lo confiesa — no una convención oral ni un `throw` sorpresa. | `await-requires-result`, `no-try-catch`, `no-promise-chain`, `no-ad-hoc-ok-result`, `@typescript-eslint/no-floating-promises` |
101
+ | A1 | **Los estados imposibles son irrepresentables.** El tipo modela exactamente los estados válidos; lo inválido no compila. | `prefer-tagged-union-state`, `no-runtime-state-guard`, `requires-strict-tsconfig`, `no-impossible-branch`, `no-explicit-any`, `prefer-type-over-interface` |
102
+ | A2 | **Ningún efecto es invisible al tipo.** Si una operación puede fallar, su firma lo confiesa — no una convención oral ni un `throw` sorpresa. | `await-requires-result`, `no-try-catch`, `no-promise-chain`, `no-ad-hoc-ok-result`, `no-floating-promises` |
103
103
  | A3 | **La información no se destruye.** Un error que se transforma conserva su `cause`; uno que se detecta llega a alguien. Nadie decide "esto no importa" en silencio. | `result-error-requires-cause`, `result-error-requires-handling` |
104
104
  | A4 | **Una unidad, una responsabilidad, un nombre semántico.** El árbol de archivos cuenta una historia; una clase expone una intención. | `one-root-function-per-file`, `max-public-methods`, `no-default-export`, `jsx-return-name-pascal-case`, `max-hook-size` |
105
- | A5 | **Las decisiones se declaran, no se interpretan.** Cada rama es explícita y exhaustiva; un caso ignorado es una decisión visible, no un hueco. | `no-else`, `no-nested-if`, `prefer-ts-pattern`, `@typescript-eslint/ban-ts-comment`, el `void promesa()` de `no-floating-promises` |
105
+ | A5 | **Las decisiones se declaran, no se interpretan.** Cada rama es explícita y exhaustiva; un caso ignorado es una decisión visible, no un hueco. | `no-else`, `no-nested-if`, `prefer-ts-pattern`, `no-silenced-compiler`, el `void promesa()` de `no-floating-promises` |
106
106
  | A6 | **Evidencia sobre convención.** Una regla decide por lo que el type-checker o los imports demuestran, no por cómo se llama un archivo o un campo. | la implementación type-aware de las reglas de Result, `nest-no-direct-instantiation` (@Injectable resuelto por símbolo), la exención ORM por decorador de `class-properties-require-readonly` |
107
107
  | A7 | **Las fronteras son explícitas y únicas.** Lo que cruza una capa lo hace por un contrato, una sola vez, sin túneles. | `no-deep-relative-imports`, `no-tunnel-props`, `nest-no-result-response`, `nest-no-swagger-in-controllers`, `nest-no-inline-query-params` |
108
108
  | A8 | **Inmutable por defecto.** La mutación es la excepción que se pide con evidencia, no el estado natural de las cosas. | `class-properties-require-readonly`, `no-accessors` |
@@ -274,6 +274,150 @@ hay errores, sale con código `1` (apto para CI). Como acota por **archivo
274
274
  completo**, también dispara las reglas estructurales (p. ej.
275
275
  `one-root-function-per-file`), que un filtrado por línea se perdería.
276
276
 
277
+ ## Adopción en proyectos legacy: de `off` a `error`, por olas
278
+
279
+ El CLI de arriba acota **qué archivos** se juzgan. Este apartado acota **qué
280
+ reglas** — el camino para meter el preset completo en un proyecto legacy
281
+ escrito por humanos, sin que el primer `pnpm lint` escupa 2.000 errores y el
282
+ equipo apague el linter para siempre.
283
+
284
+ ### Las reglas del juego
285
+
286
+ 1. **`off` o `error`, nunca `warn`.** Un warn se ignora desde el día dos y
287
+ solo entrena al equipo a ignorar amarillo. Una regla está adoptada
288
+ (`error`) o todavía no (`off`) — no hay estado intermedio.
289
+ 2. **Una regla a la vez, y a cero.** Se activa una regla, se arreglan TODOS
290
+ sus hallazgos, se mergea en verde. Nunca actives una regla con pendientes:
291
+ el CI rojo permanente es la ventana rota que normaliza ignorar el linter.
292
+ 3. **Ratchet: lo que se enciende no se apaga.** El bloque de `off` solo puede
293
+ encoger. El diff de ese bloque ES la métrica de progreso del equipo.
294
+ 4. **Mide antes de activar.** En una rama, borra el `off` de una regla y corre
295
+ el lint: el número de hallazgos es el precio. **Activa siempre la más
296
+ barata pendiente** — el momentum importa más que el orden perfecto.
297
+ 5. **Deja que el mensaje enseñe.** Los mensajes de error de estas reglas
298
+ explican el porqué y el cómo (qué patrón usar, cómo se llama, dónde va).
299
+ Para un equipo sin seniors, el linter es el code review que nadie tiene
300
+ tiempo de hacer: no resumas las reglas en un documento aparte — el
301
+ documento es el error en pantalla.
302
+
303
+ ### El mecanismo: la lista de pendientes
304
+
305
+ El preset completo es la meta; un bloque posterior apaga lo que el equipo aún
306
+ no cumple. **Adoptar una regla = borrar su línea y arreglar lo que aflore:**
307
+
308
+ ```js
309
+ // eslint.config.js
310
+ import skapxd from "@skapxd/eslint-opinionated";
311
+
312
+ export default [
313
+ ...skapxd.configs.nest, // la meta: el preset completo, desde el día uno
314
+
315
+ // ─── Lista de pendientes ───────────────────────────────────────────
316
+ // Todo lo que el proyecto aún no cumple, apagado y a la vista.
317
+ // Este bloque SOLO ENCOGE: se borra una línea, se arregla, se mergea.
318
+ {
319
+ rules: {
320
+ "skapxd/await-requires-result": "off",
321
+ "skapxd/no-try-catch": "off",
322
+ // ...
323
+ },
324
+ },
325
+ ];
326
+ ```
327
+
328
+ ### El orden de las olas
329
+
330
+ El orden no es arbitrario: va de "cada hallazgo es un bug que ya tienes" hacia
331
+ "esto exige rediseñar tipos", y cada ola deja el suelo que la siguiente pisa.
332
+
333
+ **Ola 1 — bugs gratis y fixes únicos.** Señal pura, arreglo puntual, cero
334
+ rediseño. Aquí el equipo aprende que el linter encuentra cosas reales:
335
+
336
+ - `skapxd/no-floating-promises` — cada hallazgo es un error que
337
+ hoy muere sin que nadie lo vea (en un backend real en producción: 12).
338
+ - `skapxd/nest-requires-swagger-plugin` y `skapxd/nest-validation-pipe-config`
339
+ — un hallazgo por proyecto, un fix de configuración, y quedan vigiladas las
340
+ premisas de las olas siguientes.
341
+ - `skapxd/requires-strict-tsconfig` con la exigencia mínima:
342
+ `{ requiredCompilerOptions: ["strict"] }`. Es el trinquete del tsconfig —
343
+ cada ola le sube un flag (ver abajo).
344
+ - `skapxd/no-emoji`, `skapxd/no-deep-relative-imports` — fixes mecánicos.
345
+ - `skapxd/prefer-abort-signal` (front) — cada hallazgo es un leak.
346
+
347
+ **Ola 2 — la forma del código.** Refactors locales, archivo por archivo, sin
348
+ tocar contratos. Es la ola que más enseña por repetición:
349
+
350
+ - `skapxd/no-nested-if` y `skapxd/no-else` — guard clauses. El refactor más
351
+ formativo que existe para un junior: aplana la lógica o confiesa que la
352
+ función hace demasiado.
353
+ - `skapxd/one-root-function-per-file` y `skapxd/no-default-export` — el árbol
354
+ de archivos empieza a contar la historia.
355
+ - `skapxd/no-accessors`, `skapxd/max-public-methods` — clases con una
356
+ intención (partir un god-object es la cirugía mayor de esta ola: déjala de
357
+ última).
358
+ - Front: `skapxd/jsx-return-name-pascal-case`, `skapxd/max-hook-size`,
359
+ `skapxd/no-functions-inside-components`, `skapxd/no-jsx-ternary-null`,
360
+ `skapxd/no-tunnel-props`.
361
+ - Nest: `skapxd/nest-no-swagger-in-controllers`,
362
+ `skapxd/nest-dto-requires-api-property`,
363
+ `skapxd/nest-dto-requires-validation`,
364
+ `skapxd/nest-no-inline-query-params`,
365
+ `skapxd/nest-no-direct-instantiation` — mover decoradores y dependencias a
366
+ donde pertenecen.
367
+
368
+ **Ola 3 — el contrato de errores.** La migración de paradigma
369
+ (`@skapxd/result` + `ts-pattern`; ver "Cómo encaja todo" abajo). Aquí NO se va
370
+ regla por regla sino **módulo por módulo**: las seis reglas entran juntas
371
+ (son un solo sistema) pero acotadas por carpeta, y el primer módulo migrado se
372
+ vuelve el ejemplo canónico que el resto copia:
373
+
374
+ ```js
375
+ // Ola 3: el pipeline de Result entra carpeta por carpeta.
376
+ {
377
+ files: ["src/modules/payments/**"],
378
+ rules: {
379
+ "skapxd/await-requires-result": "error",
380
+ "skapxd/no-try-catch": "error",
381
+ "skapxd/no-promise-chain": "error",
382
+ "skapxd/no-ad-hoc-ok-result": "error",
383
+ "skapxd/prefer-ts-pattern": "error",
384
+ "skapxd/result-error-requires-cause": "error",
385
+ "skapxd/result-error-requires-handling": "error",
386
+ },
387
+ },
388
+ ```
389
+
390
+ (En Nest, suma `skapxd/nest-no-result-response` al grupo: el controller del
391
+ módulo migrado traduce el Result, no lo serializa.) Cuando todos los módulos
392
+ migraron, las líneas salen del bloque por-carpeta y entran globales: se borran
393
+ de la lista de pendientes.
394
+
395
+ **Ola 4 — el modelado de estados.** Lo más profundo: exige criterio de
396
+ diseño, no solo disciplina. Para cuando el equipo ya vio el patrón en la ola 3:
397
+
398
+ - `requires-strict-tsconfig` al máximo: `["strict", "noImplicitReturns",
399
+ "noUncheckedIndexedAccess"]`. Sube un flag a la vez — cada uno aflora
400
+ errores de compilación que son bugs latentes, no burocracia.
401
+ - `skapxd/no-explicit-any`, `skapxd/no-non-null-assertion` y
402
+ `skapxd/no-silenced-compiler` — se cierran las tres puertas de escape del
403
+ compilador.
404
+ - `skapxd/class-properties-require-readonly` — el cambio se modela con
405
+ instancias nuevas.
406
+ - `skapxd/prefer-tagged-union-state` y `skapxd/no-runtime-state-guard` — los
407
+ booleanos co-dependientes se vuelven uniones etiquetadas.
408
+ - `skapxd/no-impossible-branch` — **la última de todas**: solo es sólida
409
+ cuando el tsconfig ya está al máximo (sin `noUncheckedIndexedAccess`,
410
+ acusaría guards necesarios).
411
+
412
+ ### Los dos ejes se combinan
413
+
414
+ Mientras la lista de pendientes encoge, `skapxd-lint-changed` aplica lo ya
415
+ activado solo a los archivos tocados: el código nuevo nace cumpliendo y el
416
+ legacy se corrige cuando alguien lo visita (regla del boy scout), no en un
417
+ big-bang. Un proyecto mediano recorre las cuatro olas en semanas, no en
418
+ trimestres — y cada semana el lint encuentra menos, porque el equipo ya
419
+ escribe distinto.
420
+
277
421
  ## Cómo encaja todo: `@skapxd/result` + `ts-pattern`
278
422
 
279
423
  Este plugin no es una colección de reglas sueltas: es el guardián de un
@@ -534,7 +678,7 @@ Detalles del preset:
534
678
  se escribe `void bootstrap();` — fire-and-forget declarado.
535
679
  - Los specs colocados (`*.spec.ts`, `*.e2e-spec.ts`) relajan
536
680
  `await-requires-result`, `no-try-catch`, `result-error-requires-handling` y
537
- `@typescript-eslint/no-non-null-assertion` (el `!` sobre un fixture es el
681
+ `no-non-null-assertion` (el `!` sobre un fixture es el
538
682
  arrange del test): un test awaitea helpers libremente y descartar un Result
539
683
  en una aserción no es perder un trace. `no-floating-promises` sigue activa
540
684
  en specs: un `await` olvidado es un falso verde.
@@ -676,6 +820,11 @@ de cada regla):
676
820
  | `no-default-export` | `allowFilePatterns` (globs, aditivos a los integrados) |
677
821
  | `no-else` | `allowFilePatterns` (globs) |
678
822
  | `no-emoji` | `allowFilePatterns` (globs) |
823
+ | `no-explicit-any` | las de la regla original de typescript-eslint (`fixToUnknown`, ...) |
824
+ | `no-floating-promises` | las de la regla original de typescript-eslint (`ignoreVoid`, `allowList`, ...) |
825
+ | `no-impossible-branch` | las de la regla original de typescript-eslint (`allowConstantLoopConditions`, ...) |
826
+ | `no-silenced-compiler` | las de `ban-ts-comment` (`ts-expect-error`, `ts-ignore`, `ts-nocheck`, `minimumDescriptionLength`) |
827
+ | `prefer-type-over-interface` | la de `consistent-type-definitions` (`"type"` o `"interface"`; los presets pasan `"type"`) |
679
828
  | `no-functions-inside-components` | `allowJsxCallbacks`, `allowArrayMapCallbacks` (ambas `true` por defecto) |
680
829
  | `no-nested-if` | `allowFilePatterns` (globs) |
681
830
  | `no-promise-chain` | `methods` |
@@ -719,11 +868,17 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
719
868
  | `skapxd/no-default-export` | Prohíbe `export default`; el nombre del símbolo es el contrato. Exime configs/stories y, en el preset `next`, los entrypoints del App Router. |
720
869
  | `skapxd/no-else` | Prohíbe `else`/`else if`: el else es el estado sin nombre. Retorno anticipado, ternario simple o `match()`. |
721
870
  | `skapxd/no-emoji` | Prohíbe emojis en strings y JSX; cada sistema los renderiza distinto. Usa un icono SVG. |
871
+ | `skapxd/no-explicit-any` | Prohíbe `any`: apaga el sistema de tipos donde más se necesita. `unknown` para lo desconocido, el tipo real para lo demás. Wrapper de typescript-eslint. |
872
+ | `skapxd/no-floating-promises` | Promesas sin `await` ni `void`: el rechazo muere sin pasar por trySafe. El mensaje corrige el consejo upstream (`.then/.catch` aquí están prohibidos). Wrapper de typescript-eslint. |
873
+ | `skapxd/no-impossible-branch` | Condiciones que el type-checker demuestra constantes: la pregunta ya tiene respuesta. Es `@typescript-eslint/no-unnecessary-condition` con nombre semántico y mensajes que enseñan el fix. |
722
874
  | `skapxd/no-nested-if` | Prohíbe `if` anidados: retorno anticipado o `match()`. Menos carga cognitiva y sin puntos ciegos para las demás reglas. |
875
+ | `skapxd/no-non-null-assertion` | Prohíbe el `!`: es "cállate, yo sé más que tú" dicho al compilador. Modela el tipo o maneja la duda. Wrapper de typescript-eslint. |
723
876
  | `skapxd/no-runtime-state-guard` | Prohíbe `if (this.x) throw` en métodos: el estado inválido se hace irrepresentable en el tipo, no se vigila en runtime. |
877
+ | `skapxd/no-silenced-compiler` | Prohíbe `@ts-ignore`/`@ts-nocheck`: silenciar la alarma no arregla el incendio. `@ts-expect-error` con descripción queda para tests de tipos. Wrapper de `ban-ts-comment`. |
724
878
  | `skapxd/no-tunnel-props` | Ninguna prop viaja más de un nivel: quien la recibe no puede reenviarla a otro componente. Mata el prop drilling. |
725
879
  | `skapxd/prefer-abort-signal` | Listeners en efectos se limpian con `AbortController` (`{ signal }` + `abort()`), no con `removeEventListener`. |
726
880
  | `skapxd/prefer-tagged-union-state` | Prohíbe estados inconsistentes representables: flag de loading + campo de error independientes → unión etiquetada. |
881
+ | `skapxd/prefer-type-over-interface` | Las uniones discriminadas son types; un `type` no crece en silencio por declaration merging. Wrapper de `consistent-type-definitions`. |
727
882
  | `skapxd/no-functions-inside-components` | Prohíbe definir funciones dentro de componentes React. |
728
883
  | `skapxd/no-try-catch` | Prohíbe `try/catch`; usa `trySafe` de `@skapxd/result`. |
729
884
  | `skapxd/no-promise-chain` | Prohíbe `.then/.catch/.finally`; usa `await` (+ `trySafe`). |
@@ -831,8 +986,12 @@ Todas las opciones, con sus defaults:
831
986
  Todo el sistema descansa en que el compilador pueda hacer irrepresentables
832
987
  los estados inválidos — y eso exige un `tsconfig` implacable. Esta regla lee
833
988
  el `tsconfig.json` **real** del proyecto (con la API de TypeScript: soporta
834
- JSONC y resuelve la cadena de `extends`) y exige los flags, anclada a un
835
- entrypoint para reportar una vez por proyecto:
989
+ JSONC y resuelve la cadena de `extends`) y exige los flags, reportando una
990
+ vez por proyecto: si existe un archivo ancla (`anchorFilePatterns`, default
991
+ `src/main.ts(x)`/`src/index.ts(x)`), el reporte le pertenece a ese archivo;
992
+ si el proyecto no tiene entrypoint clásico (Astro, librerías), reporta sobre
993
+ el primer archivo del run y los demás callan — un proyecto sin ancla no se
994
+ queda sin guardián:
836
995
 
837
996
  - `strict` — sin él, el sistema de tipos está apagado a medias.
838
997
  - `noImplicitReturns` — una rama que sale sin valor deja de ser silenciosa
@@ -846,32 +1005,29 @@ Fuera del default, a propósito: `exactOptionalPropertyTypes` y
846
1005
  con muchas librerías — se agregan vía `requiredCompilerOptions` si el
847
1006
  proyecto los soporta.
848
1007
 
849
- Además, los **presets tipados activan reglas curadas de typescript-eslint**
850
- (que ya es peer dependency no se reimplementan):
851
-
852
- - `@typescript-eslint/no-explicit-any` `any` apaga el sistema de tipos:
853
- todo el esfuerzo muere donde aparece uno.
854
- - `@typescript-eslint/consistent-type-definitions` con `type` — las uniones
855
- discriminadas son types.
856
- - `@typescript-eslint/no-floating-promises` cierra el hueco que
857
- `await-requires-result` no ve: una llamada async **sin** `await` no produce
858
- `AwaitExpression`, así que el rechazo muere sin pasar por `trySafe`
859
- (medido: 12 promesas flotantes vivas en un backend Nest real). La única
860
- salida es `void promesa()`: fire-and-forget declarado y greppeable.
861
- - `@typescript-eslint/no-non-null-assertion` `!` es "cállate, yo más
862
- que tú" dicho al compilador. Si no puede ser nulo, que lo diga el tipo.
863
- (En `nest/tests` queda apagada: el `!` sobre un fixture cuya existencia el
864
- propio test garantiza es el arrange, no una mentira.)
865
- - `@typescript-eslint/no-unnecessary-condition` la generalización
866
- type-aware de `no-runtime-state-guard`: si el tipo dice que un estado es
867
- imposible, el guard defensivo sobra; si el guard hace falta, lo que está
868
- mal es el tipo. Por eso esta regla y `requires-strict-tsconfig` van
869
- juntas: sin `noUncheckedIndexedAccess`, `array[i]` miente y la regla
870
- acusaría guards necesarios.
871
- - `@typescript-eslint/ban-ts-comment` un error de tipos se arregla
872
- modelando mejor, no silenciando la alarma: `@ts-ignore` y `@ts-nocheck`
873
- prohibidos. `@ts-expect-error` **con descripción** queda permitido: es la
874
- forma legítima de testear que un estado inválido de verdad no compila.
1008
+ Además, los **presets tipados activan reglas curadas de typescript-eslint**,
1009
+ todas **re-registradas bajo el namespace skapxd** (mismo motor, cero
1010
+ reimplementación — typescript-eslint ya es peer dependency): nombres que
1011
+ dicen lo que defienden, mensajes en español que enseñan el fix, y un solo
1012
+ namespace en toda tu lista de pendientes. Cada una tiene su sección propia:
1013
+
1014
+ - `skapxd/no-explicit-any` — `any` apaga el sistema de tipos: todo el
1015
+ esfuerzo muere donde aparece uno.
1016
+ - `skapxd/prefer-type-over-interface` (era `consistent-type-definitions`)
1017
+ las uniones discriminadas son types.
1018
+ - `skapxd/no-floating-promises` cierra el hueco que `await-requires-result`
1019
+ no ve: una llamada async **sin** `await` no produce `AwaitExpression`, así
1020
+ que el rechazo muere sin pasar por `trySafe` (medido: 12 promesas
1021
+ flotantes vivas en un backend Nest real).
1022
+ - `skapxd/no-non-null-assertion` `!` es "cállate, yo más que tú" dicho
1023
+ al compilador. (En `nest/tests` queda apagada: el `!` sobre un fixture es
1024
+ el arrange, no una mentira.)
1025
+ - `skapxd/no-impossible-branch` (era `no-unnecessary-condition`) la
1026
+ generalización type-aware de `no-runtime-state-guard`. Va de la mano de
1027
+ `requires-strict-tsconfig`: sin `noUncheckedIndexedAccess`, `array[i]`
1028
+ miente y la regla acusaría guards necesarios.
1029
+ - `skapxd/no-silenced-compiler` (era `ban-ts-comment`) — un error de tipos
1030
+ se arregla modelando mejor, no silenciando la alarma.
875
1031
 
876
1032
  Ausencias deliberadas, no olvidos:
877
1033
 
@@ -1726,6 +1882,113 @@ en un `<input>` es la frontera con el DOM). Para wrappers legítimos de un
1726
1882
  design system, exime props por nombre (`allowPropPatterns: ["^className$"]`)
1727
1883
  o archivos completos (`allowFilePatterns`).
1728
1884
 
1885
+ ### `skapxd/no-impossible-branch`
1886
+
1887
+ La rama imposible: una condición que el type-checker demuestra constante. Si
1888
+ el tipo dice que un valor siempre es truthy, ese `if` no decide nada; si un
1889
+ `?.` cuelga de algo que nunca es nullish, finge una duda que el modelo ya
1890
+ resolvió. La pregunta ya tiene respuesta — y un `if` que no pregunta es
1891
+ código muerto disfrazado de prudencia.
1892
+
1893
+ ```ts
1894
+ const sheet = workbook.Sheets[name]; // tipo: WorkSheet (¿seguro?)
1895
+ if (!sheet) continue; // ❌ "always falsy"... ¿o el tipo miente?
1896
+ ```
1897
+
1898
+ El mensaje de error enseña la lección completa: **si la comprobación hace
1899
+ falta en runtime, lo que está mal es el tipo**. El caso clásico es el acceso
1900
+ por índice sin `noUncheckedIndexedAccess` — `array[i]` y `obj[key]` juran que
1901
+ nunca son `undefined`, y esta regla, creyéndoles, acusaría guards necesarios.
1902
+ Por eso va de la mano de `skapxd/requires-strict-tsconfig`, que exige ese
1903
+ flag: primero el tsconfig dice la verdad, después esta regla opina.
1904
+
1905
+ Bajo el capó es `@typescript-eslint/no-unnecessary-condition`
1906
+ ([doc original](https://typescript-eslint.io/rules/no-unnecessary-condition/))
1907
+ **re-registrada bajo nuestro namespace**: mismo motor y mismas opciones, pero
1908
+ con un nombre que dice lo que defiende (axioma A1: los estados imposibles son
1909
+ irrepresentables — es la generalización type-aware de
1910
+ `no-runtime-state-guard`) y mensajes en español que explican el fix en vez
1911
+ del críptico "Unnecessary conditional". Los presets tipados activan este
1912
+ nombre y **no** el original: una sola fuente de verdad para configurarla,
1913
+ silenciarla o buscarla.
1914
+
1915
+ ### `skapxd/no-explicit-any`
1916
+
1917
+ Prohíbe `any`. No es una regla de estilo: `any` apaga el sistema de tipos en
1918
+ todo lo que toca — el esfuerzo de modelar estados imposibles muere donde
1919
+ aparece uno, y se propaga en silencio a cada valor derivado. El mensaje
1920
+ enseña la salida: `unknown` para lo genuinamente desconocido (obliga a
1921
+ estrechar antes de usar — la duda queda declarada y verificada), el tipo
1922
+ real para lo que tiene forma conocida.
1923
+
1924
+ Bajo el capó es `@typescript-eslint/no-explicit-any`
1925
+ ([doc original](https://typescript-eslint.io/rules/no-explicit-any/))
1926
+ re-registrada bajo nuestro namespace con mensajes que enseñan (ver
1927
+ `skapxd/no-impossible-branch` para el patrón). Los presets tipados activan
1928
+ este nombre, no el original.
1929
+
1930
+ ### `skapxd/no-floating-promises`
1931
+
1932
+ Una llamada async **sin** `await` no produce `AwaitExpression` — es el punto
1933
+ ciego de `await-requires-result`: el rechazo muere sin pasar por `trySafe`,
1934
+ sin trace y sin que nadie lo decidiera (medido al absorberla: 12 promesas
1935
+ flotantes vivas en un backend en producción).
1936
+
1937
+ Esta regla existía en typescript-eslint
1938
+ ([doc original](https://typescript-eslint.io/rules/no-floating-promises/)),
1939
+ pero su mensaje recomendaba *"end with a call to `.catch`, or end with a
1940
+ call to `.then` with a rejection handler"* — **dos caminos que
1941
+ `no-promise-chain` prohíbe**. Obedecer a una regla te estrellaba con la
1942
+ otra. El wrapper corrige el consejo para este sistema: las dos salidas
1943
+ legales son `await` (y ahí entra el pipeline de Result) o `void promesa()`
1944
+ — el fire-and-forget declarado y greppeable del axioma A5 (así se escribe
1945
+ el `bootstrap()` del `main.ts` de Nest: `void bootstrap();`).
1946
+
1947
+ ### `skapxd/no-non-null-assertion`
1948
+
1949
+ Prohíbe el `!` (non-null assertion): es "cállate, yo sé más que tú" dicho al
1950
+ compilador — y un `!` equivocado es un crash en runtime que el tipo juraba
1951
+ imposible. Si el valor de verdad no puede ser nulo, que lo diga el tipo
1952
+ (modela mejor, o estrecha con un guard que el compilador verifique); si
1953
+ puede serlo, el `!` no resuelve la duda: la esconde.
1954
+
1955
+ La excepción legítima vive en los tests: el `!` sobre un fixture cuya
1956
+ existencia el propio test garantiza es el arrange, no una mentira — por eso
1957
+ `nest/tests` la apaga en specs. Bajo el capó es
1958
+ `@typescript-eslint/no-non-null-assertion`
1959
+ ([doc original](https://typescript-eslint.io/rules/no-non-null-assertion/))
1960
+ re-registrada con mensajes propios.
1961
+
1962
+ ### `skapxd/no-silenced-compiler`
1963
+
1964
+ No silencies al compilador: `@ts-ignore` y `@ts-nocheck` apagan la alarma en
1965
+ vez de arreglar el incendio. Si el compilador es el muro de contención del
1966
+ sistema, nadie lo apaga cuando el modelado se pone difícil — un error de
1967
+ tipos se resuelve modelando mejor el dominio.
1968
+
1969
+ La puerta que queda abierta, a propósito: `@ts-expect-error` **con
1970
+ descripción**. Es la forma legítima de testear que un estado inválido de
1971
+ verdad NO compila (la otra mitad son los tests de tipos con `expectTypeOf`,
1972
+ ver la sección de `requires-strict-tsconfig`) — y a diferencia de
1973
+ `@ts-ignore`, avisa cuando la supresión deja de hacer falta. Bajo el capó es
1974
+ `@typescript-eslint/ban-ts-comment`
1975
+ ([doc original](https://typescript-eslint.io/rules/ban-ts-comment/)) con un
1976
+ nombre que dice lo que defiende y mensajes propios.
1977
+
1978
+ ### `skapxd/prefer-type-over-interface`
1979
+
1980
+ Usa `type`, no `interface`. Las uniones discriminadas — la columna vertebral
1981
+ del modelado de estados de este paquete — son types, y la homogeneidad
1982
+ elimina la pregunta "¿esto puede crecer por declaration merging?": un `type`
1983
+ no puede ser extendido en silencio desde otro archivo; lo que declara es
1984
+ todo lo que hay.
1985
+
1986
+ Bajo el capó es `@typescript-eslint/consistent-type-definitions`
1987
+ ([doc original](https://typescript-eslint.io/rules/consistent-type-definitions/))
1988
+ re-registrada con un nombre que declara la opinión (como los demás
1989
+ `prefer-*`). Ojo si la activas suelta: el default upstream prefiere
1990
+ `interface` — los presets la pasan como `["error", "type"]`.
1991
+
1729
1992
  ### `skapxd/no-functions-inside-components`
1730
1993
 
1731
1994
  Prohíbe definir funciones **con peso propio** dentro de un componente React
@@ -64,9 +64,6 @@ function createBaseLanguageOptions() {
64
64
  };
65
65
  }
66
66
 
67
- // src/shared/configs/create-shared-configs.ts
68
- var import_typescript_eslint3 = __toESM(require("typescript-eslint"));
69
-
70
67
  // src/shared/configs/create-typed-language-options.ts
71
68
  var import_typescript_eslint2 = __toESM(require("typescript-eslint"));
72
69
  function createTypedLanguageOptions() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/astro/index.ts","../../src/shared/configs/base-rules.ts","../../src/shared/configs/create-base-language-options.ts","../../src/shared/configs/create-shared-configs.ts","../../src/shared/configs/create-typed-language-options.ts","../../src/astro/configs.ts"],"sourcesContent":["export { createAstroConfigs } from \"./configs\";\n","export const baseRules = {\n \"skapxd/class-properties-require-readonly\": \"error\",\n \"skapxd/max-public-methods\": \"error\",\n \"skapxd/no-accessors\": \"error\",\n \"skapxd/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-default-export\": \"error\",\n \"skapxd/no-else\": \"error\",\n \"skapxd/no-emoji\": \"error\",\n \"skapxd/no-nested-if\": \"error\",\n \"skapxd/no-runtime-state-guard\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-tagged-union-state\": \"error\",\n \"skapxd/requires-strict-tsconfig\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n \"skapxd/result-error-requires-handling\": \"error\",\n};\n","import tseslint from \"typescript-eslint\";\n\n// Variante sin type-checking: solo el parser, para presets que aplican a TS\n// pero no necesitan projectService (base, package, next/base, next/react).\nexport function createBaseLanguageOptions() {\n return {\n parser: tseslint.parser,\n };\n}\n","import tseslint from \"typescript-eslint\";\nimport { baseRules } from \"./base-rules\";\nimport { createBaseLanguageOptions } from \"./create-base-language-options\";\nimport { createTypedLanguageOptions } from \"./create-typed-language-options\";\nimport { typeDrivenRules } from \"./type-driven-rules\";\n\nexport function createSharedConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return {\n backend: {\n languageOptions: typedLanguageOptions,\n name: \"skapxd/shared/backend\",\n plugins: {\n \"@typescript-eslint\": tseslint.plugin,\n skapxd: pluginReference,\n },\n rules: {\n ...baseRules,\n ...typeDrivenRules,\n // La regla obligatoria del sistema de errores es await-requires-result\n // (todo await resuelve en Result). async-functions-return-result queda\n // apagada por defecto: exigir la firma por decreto choca con los bordes\n // del framework y bloquea la adopción incremental; la presión sobre los\n // awaits produce el mismo estado final. Ver \"¿Por qué está apagada por\n // defecto?\" en el README.\n \"skapxd/await-requires-result\": \"error\",\n },\n },\n base: {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/shared/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n frontend: {\n languageOptions: typedLanguageOptions,\n name: \"skapxd/shared/frontend\",\n plugins: {\n \"@typescript-eslint\": tseslint.plugin,\n skapxd: pluginReference,\n },\n rules: {\n ...baseRules,\n ...typeDrivenRules,\n // En el front no se obliga a retornar Result, pero todo await debe\n // resolver en uno: o la función llamada ya retorna Promise<Result>\n // (camino preferido: errores modelados en el dominio) o se envuelve\n // en trySafe en el sitio.\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/jsx-return-name-pascal-case\": \"error\",\n // Anti prop-drilling: ninguna prop viaja más de un nivel. Quien la\n // crea puede pasarla a UN hijo; quien la recibe no la reenvía —\n // estado y acciones a un store global o custom hook.\n \"skapxd/no-functions-inside-components\": \"error\",\n \"skapxd/no-tunnel-props\": \"error\",\n // Listeners en efectos: un AbortController por efecto, cleanup con\n // un solo abort() en vez de removeEventListener por listener.\n \"skapxd/prefer-abort-signal\": \"error\",\n \"skapxd/no-jsx-ternary-null\": \"error\",\n \"skapxd/max-hook-size\": [\n \"error\",\n {\n maxLines: 120,\n maxUseState: 1,\n },\n ],\n },\n },\n package: {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/shared/package\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/one-root-function-per-file\": \"error\",\n },\n },\n };\n}\n","import tseslint from \"typescript-eslint\";\n\nexport function createTypedLanguageOptions() {\n return {\n // Sin el parser explícito, un consumidor que use solo estos presets\n // obtiene \"Parsing error\" en cada archivo TS (espree no parsea TS).\n parser: tseslint.parser,\n parserOptions: {\n projectService: true,\n },\n };\n}\n","import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createAstroConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/astro/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n // Los .astro no llevan parser propio: lo aporta eslint-plugin-astro,\n // que el consumidor debe tener configurado.\n {\n files: [\"src/**/*.astro\"],\n name: \"skapxd/astro/astro-files\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/astro/typescript\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n \"skapxd/result-error-requires-handling\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,YAAY;AAAA,EACvB,4CAA4C;AAAA,EAC5C,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,iCAAiC;AAAA,EACjC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,oCAAoC;AAAA,EACpC,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,sCAAsC;AAAA,EACtC,yCAAyC;AAC3C;;;ACnBA,+BAAqB;AAId,SAAS,4BAA4B;AAC1C,SAAO;AAAA,IACL,QAAQ,yBAAAA,QAAS;AAAA,EACnB;AACF;;;ACRA,IAAAC,4BAAqB;;;ACArB,IAAAC,4BAAqB;AAEd,SAAS,6BAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA,IAGL,QAAQ,0BAAAC,QAAS;AAAA,IACjB,eAAe;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACLO,SAAS,mBAAmB,iBAA0B;AAC3D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,MACE,OAAO,CAAC,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,gCAAgC;AAAA,QAChC,sCAAsC;AAAA,QACtC,yCAAyC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AACF;","names":["tseslint","import_typescript_eslint","import_typescript_eslint","tseslint"]}
1
+ {"version":3,"sources":["../../src/astro/index.ts","../../src/shared/configs/base-rules.ts","../../src/shared/configs/create-base-language-options.ts","../../src/shared/configs/create-typed-language-options.ts","../../src/astro/configs.ts"],"sourcesContent":["export { createAstroConfigs } from \"./configs\";\n","export const baseRules = {\n \"skapxd/class-properties-require-readonly\": \"error\",\n \"skapxd/max-public-methods\": \"error\",\n \"skapxd/no-accessors\": \"error\",\n \"skapxd/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-default-export\": \"error\",\n \"skapxd/no-else\": \"error\",\n \"skapxd/no-emoji\": \"error\",\n \"skapxd/no-nested-if\": \"error\",\n \"skapxd/no-runtime-state-guard\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-tagged-union-state\": \"error\",\n \"skapxd/requires-strict-tsconfig\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n \"skapxd/result-error-requires-handling\": \"error\",\n};\n","import tseslint from \"typescript-eslint\";\n\n// Variante sin type-checking: solo el parser, para presets que aplican a TS\n// pero no necesitan projectService (base, package, next/base, next/react).\nexport function createBaseLanguageOptions() {\n return {\n parser: tseslint.parser,\n };\n}\n","import tseslint from \"typescript-eslint\";\n\nexport function createTypedLanguageOptions() {\n return {\n // Sin el parser explícito, un consumidor que use solo estos presets\n // obtiene \"Parsing error\" en cada archivo TS (espree no parsea TS).\n parser: tseslint.parser,\n parserOptions: {\n projectService: true,\n },\n };\n}\n","import {\n baseRules,\n createBaseLanguageOptions,\n createTypedLanguageOptions,\n} from \"#/shared/configs\";\n\nexport function createAstroConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return [\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: baseLanguageOptions,\n name: \"skapxd/astro/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n // Los .astro no llevan parser propio: lo aporta eslint-plugin-astro,\n // que el consumidor debe tener configurado.\n {\n files: [\"src/**/*.astro\"],\n name: \"skapxd/astro/astro-files\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n {\n files: [\"src/**/*.{ts,tsx}\"],\n languageOptions: typedLanguageOptions,\n name: \"skapxd/astro/typescript\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n \"skapxd/result-error-requires-handling\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,YAAY;AAAA,EACvB,4CAA4C;AAAA,EAC5C,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,iCAAiC;AAAA,EACjC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,oCAAoC;AAAA,EACpC,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,sCAAsC;AAAA,EACtC,yCAAyC;AAC3C;;;ACnBA,+BAAqB;AAId,SAAS,4BAA4B;AAC1C,SAAO;AAAA,IACL,QAAQ,yBAAAA,QAAS;AAAA,EACnB;AACF;;;ACRA,IAAAC,4BAAqB;AAEd,SAAS,6BAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA,IAGL,QAAQ,0BAAAC,QAAS;AAAA,IACjB,eAAe;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACLO,SAAS,mBAAmB,iBAA0B;AAC3D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA;AAAA;AAAA,IAGA;AAAA,MACE,OAAO,CAAC,gBAAgB;AAAA,MACxB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA;AAAA,MACE,OAAO,CAAC,mBAAmB;AAAA,MAC3B,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,gCAAgC;AAAA,QAChC,sCAAsC;AAAA,QACtC,yCAAyC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AACF;","names":["tseslint","import_typescript_eslint","tseslint"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAstroConfigs
3
- } from "../chunk-C5JP4FQN.mjs";
4
- import "../chunk-QR35MHTW.mjs";
3
+ } from "../chunk-7OIMY5TI.mjs";
4
+ import "../chunk-4FQ7SFU4.mjs";
5
5
  export {
6
6
  createAstroConfigs
7
7
  };
@@ -11,9 +11,6 @@ function createTypedLanguageOptions() {
11
11
  };
12
12
  }
13
13
 
14
- // src/shared/configs/create-shared-configs.ts
15
- import tseslint3 from "typescript-eslint";
16
-
17
14
  // src/shared/configs/base-rules.ts
18
15
  var baseRules = {
19
16
  "skapxd/class-properties-require-readonly": "error",
@@ -46,11 +43,30 @@ function createBaseLanguageOptions() {
46
43
 
47
44
  // src/shared/configs/type-driven-rules.ts
48
45
  var typeDrivenRules = {
46
+ // `any` apaga el sistema de tipos: todo el esfuerzo de modelar estados
47
+ // irrepresentables muere donde aparece uno. (Era no-explicit-any.)
48
+ "skapxd/no-explicit-any": "error",
49
+ // El hueco que await-requires-result no ve: una llamada async SIN await no
50
+ // produce AwaitExpression — el rechazo muere sin pasar por trySafe. La
51
+ // única salida sin await es `void promesa()`: fire-and-forget declarado.
52
+ // (Era no-floating-promises; el mensaje upstream recomendaba .then/.catch,
53
+ // que no-promise-chain prohíbe — el nuestro corrige el consejo.)
54
+ "skapxd/no-floating-promises": "error",
55
+ // La generalización type-aware de no-runtime-state-guard: si el tipo dice
56
+ // que un estado es imposible, el guard defensivo sobra — y si el guard
57
+ // hace falta, lo que está mal es el tipo. Requiere el tsconfig de
58
+ // requires-strict-tsconfig para ser sólida: sin noUncheckedIndexedAccess,
59
+ // `array[i]` miente y esta regla acusaría guards necesarios.
60
+ // (Era no-unnecessary-condition.)
61
+ "skapxd/no-impossible-branch": "error",
62
+ // `!` es "cállate, yo sé más que tú" dicho al compilador. Si el valor no
63
+ // puede ser nulo, que lo diga el tipo; si puede serlo, hay que modelarlo.
64
+ "skapxd/no-non-null-assertion": "error",
49
65
  // Silenciar la alarma no arregla el incendio: un error de tipos se
50
66
  // resuelve modelando mejor, no apagando el compilador. @ts-expect-error
51
67
  // queda permitido CON descripción: es la forma legítima de testear que un
52
- // estado inválido de verdad no compila.
53
- "@typescript-eslint/ban-ts-comment": [
68
+ // estado inválido de verdad no compila. (Era ban-ts-comment.)
69
+ "skapxd/no-silenced-compiler": [
54
70
  "error",
55
71
  {
56
72
  "ts-expect-error": "allow-with-description",
@@ -60,25 +76,9 @@ var typeDrivenRules = {
60
76
  ],
61
77
  // `type` en vez de `interface`: las uniones discriminadas son types, y la
62
78
  // homogeneidad evita el "¿esto se puede extender por declaration merging?"
63
- "@typescript-eslint/consistent-type-definitions": ["error", "type"],
64
- // `any` apaga el sistema de tipos: todo el esfuerzo de modelar estados
65
- // irrepresentables muere donde aparece uno.
66
- "@typescript-eslint/no-explicit-any": "error",
67
- // El hueco que await-requires-result no ve: una llamada async SIN await no
68
- // produce AwaitExpression — el rechazo muere sin pasar por trySafe. Esta
69
- // regla obliga a awaitear (y ahí entra el pipeline de Result). El operador
70
- // `void promesa` queda como única salida: fire-and-forget declarado y
71
- // greppeable, no interpretado.
72
- "@typescript-eslint/no-floating-promises": "error",
73
- // `!` es "cállate, yo sé más que tú" dicho al compilador. Si el valor no
74
- // puede ser nulo, que lo diga el tipo; si puede serlo, hay que modelarlo.
75
- "@typescript-eslint/no-non-null-assertion": "error",
76
- // La generalización type-aware de no-runtime-state-guard: si el tipo dice
77
- // que un estado es imposible, el guard defensivo sobra — y si el guard
78
- // hace falta, lo que está mal es el tipo. Requiere el tsconfig de
79
- // requires-strict-tsconfig para ser sólida: sin noUncheckedIndexedAccess,
80
- // `array[i]` miente y esta regla acusaría guards necesarios.
81
- "@typescript-eslint/no-unnecessary-condition": "error"
79
+ // (Era consistent-type-definitions, cuyo default upstream es `interface` —
80
+ // por eso la opción explícita.)
81
+ "skapxd/prefer-type-over-interface": ["error", "type"]
82
82
  };
83
83
 
84
84
  // src/shared/configs/create-shared-configs.ts
@@ -89,10 +89,11 @@ function createSharedConfigs(pluginReference) {
89
89
  backend: {
90
90
  languageOptions: typedLanguageOptions,
91
91
  name: "skapxd/shared/backend",
92
- plugins: {
93
- "@typescript-eslint": tseslint3.plugin,
94
- skapxd: pluginReference
95
- },
92
+ // Solo el plugin skapxd: las reglas de typescript-eslint entran
93
+ // re-registradas bajo nuestro namespace (typeDrivenRules), así el
94
+ // consumidor puede registrar su propia instancia de tseslint sin
95
+ // chocar con "Cannot redefine plugin".
96
+ plugins: { skapxd: pluginReference },
96
97
  rules: {
97
98
  ...baseRules,
98
99
  ...typeDrivenRules,
@@ -114,10 +115,7 @@ function createSharedConfigs(pluginReference) {
114
115
  frontend: {
115
116
  languageOptions: typedLanguageOptions,
116
117
  name: "skapxd/shared/frontend",
117
- plugins: {
118
- "@typescript-eslint": tseslint3.plugin,
119
- skapxd: pluginReference
120
- },
118
+ plugins: { skapxd: pluginReference },
121
119
  rules: {
122
120
  ...baseRules,
123
121
  ...typeDrivenRules,
@@ -172,4 +170,4 @@ export {
172
170
  createSharedConfigs,
173
171
  strictConfig
174
172
  };
175
- //# sourceMappingURL=chunk-QR35MHTW.mjs.map
173
+ //# sourceMappingURL=chunk-4FQ7SFU4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/configs/create-typed-language-options.ts","../src/shared/configs/base-rules.ts","../src/shared/configs/create-base-language-options.ts","../src/shared/configs/type-driven-rules.ts","../src/shared/configs/create-shared-configs.ts","../src/shared/configs/strict-config.ts"],"sourcesContent":["import tseslint from \"typescript-eslint\";\n\nexport function createTypedLanguageOptions() {\n return {\n // Sin el parser explícito, un consumidor que use solo estos presets\n // obtiene \"Parsing error\" en cada archivo TS (espree no parsea TS).\n parser: tseslint.parser,\n parserOptions: {\n projectService: true,\n },\n };\n}\n","export const baseRules = {\n \"skapxd/class-properties-require-readonly\": \"error\",\n \"skapxd/max-public-methods\": \"error\",\n \"skapxd/no-accessors\": \"error\",\n \"skapxd/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-default-export\": \"error\",\n \"skapxd/no-else\": \"error\",\n \"skapxd/no-emoji\": \"error\",\n \"skapxd/no-nested-if\": \"error\",\n \"skapxd/no-runtime-state-guard\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-tagged-union-state\": \"error\",\n \"skapxd/requires-strict-tsconfig\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"error\",\n \"skapxd/result-error-requires-handling\": \"error\",\n};\n","import tseslint from \"typescript-eslint\";\n\n// Variante sin type-checking: solo el parser, para presets que aplican a TS\n// pero no necesitan projectService (base, package, next/base, next/react).\nexport function createBaseLanguageOptions() {\n return {\n parser: tseslint.parser,\n };\n}\n","// Reglas de typescript-eslint que el diseño guiado por tipos exige, todas\n// re-registradas bajo el namespace skapxd (ver src/utils/wrap-tseslint-rule.ts):\n// mismo motor, cero reimplementación, pero con nombres que dicen lo que\n// defienden y mensajes que enseñan el fix — un solo namespace en toda la\n// lista de pendientes del consumidor.\n//\n// Ausencias deliberadas (no son olvidos):\n// - switch-exhaustiveness-check: prefer-ts-pattern prohíbe el switch entero;\n// match().exhaustive() da la misma garantía sin él.\n// - prefer-readonly: superada por class-properties-require-readonly, que\n// exige readonly en la declaración (no solo en privados nunca reasignados).\n// - strict-boolean-expressions: castiga narrowing legítimo por cientos\n// (560 hallazgos en un backend real) sin hacer irrepresentable ningún\n// estado nuevo. Ruido, no señal.\n// - explicit-module-boundary-types: los contratos que importan ya están\n// gobernados (Result en await-requires-result, respuestas de controller en\n// nest-no-result-response); anotar todo lo demás es ceremonia (198\n// hallazgos) que la inferencia resuelve sin perder garantías.\n// - prefer-readonly-parameter-types: impracticable con cualquier parámetro\n// que venga de una librería externa.\nexport const typeDrivenRules = {\n // `any` apaga el sistema de tipos: todo el esfuerzo de modelar estados\n // irrepresentables muere donde aparece uno. (Era no-explicit-any.)\n \"skapxd/no-explicit-any\": \"error\",\n // El hueco que await-requires-result no ve: una llamada async SIN await no\n // produce AwaitExpression — el rechazo muere sin pasar por trySafe. La\n // única salida sin await es `void promesa()`: fire-and-forget declarado.\n // (Era no-floating-promises; el mensaje upstream recomendaba .then/.catch,\n // que no-promise-chain prohíbe — el nuestro corrige el consejo.)\n \"skapxd/no-floating-promises\": \"error\",\n // La generalización type-aware de no-runtime-state-guard: si el tipo dice\n // que un estado es imposible, el guard defensivo sobra — y si el guard\n // hace falta, lo que está mal es el tipo. Requiere el tsconfig de\n // requires-strict-tsconfig para ser sólida: sin noUncheckedIndexedAccess,\n // `array[i]` miente y esta regla acusaría guards necesarios.\n // (Era no-unnecessary-condition.)\n \"skapxd/no-impossible-branch\": \"error\",\n // `!` es \"cállate, yo sé más que tú\" dicho al compilador. Si el valor no\n // puede ser nulo, que lo diga el tipo; si puede serlo, hay que modelarlo.\n \"skapxd/no-non-null-assertion\": \"error\",\n // Silenciar la alarma no arregla el incendio: un error de tipos se\n // resuelve modelando mejor, no apagando el compilador. @ts-expect-error\n // queda permitido CON descripción: es la forma legítima de testear que un\n // estado inválido de verdad no compila. (Era ban-ts-comment.)\n \"skapxd/no-silenced-compiler\": [\n \"error\",\n {\n \"ts-expect-error\": \"allow-with-description\",\n \"ts-ignore\": true,\n \"ts-nocheck\": true,\n },\n ],\n // `type` en vez de `interface`: las uniones discriminadas son types, y la\n // homogeneidad evita el \"¿esto se puede extender por declaration merging?\"\n // (Era consistent-type-definitions, cuyo default upstream es `interface` —\n // por eso la opción explícita.)\n \"skapxd/prefer-type-over-interface\": [\"error\", \"type\"],\n};\n","import { baseRules } from \"./base-rules\";\nimport { createBaseLanguageOptions } from \"./create-base-language-options\";\nimport { createTypedLanguageOptions } from \"./create-typed-language-options\";\nimport { typeDrivenRules } from \"./type-driven-rules\";\n\nexport function createSharedConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return {\n backend: {\n languageOptions: typedLanguageOptions,\n name: \"skapxd/shared/backend\",\n // Solo el plugin skapxd: las reglas de typescript-eslint entran\n // re-registradas bajo nuestro namespace (typeDrivenRules), así el\n // consumidor puede registrar su propia instancia de tseslint sin\n // chocar con \"Cannot redefine plugin\".\n plugins: { skapxd: pluginReference },\n rules: {\n ...baseRules,\n ...typeDrivenRules,\n // La regla obligatoria del sistema de errores es await-requires-result\n // (todo await resuelve en Result). async-functions-return-result queda\n // apagada por defecto: exigir la firma por decreto choca con los bordes\n // del framework y bloquea la adopción incremental; la presión sobre los\n // awaits produce el mismo estado final. Ver \"¿Por qué está apagada por\n // defecto?\" en el README.\n \"skapxd/await-requires-result\": \"error\",\n },\n },\n base: {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/shared/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n frontend: {\n languageOptions: typedLanguageOptions,\n name: \"skapxd/shared/frontend\",\n plugins: { skapxd: pluginReference },\n rules: {\n ...baseRules,\n ...typeDrivenRules,\n // En el front no se obliga a retornar Result, pero todo await debe\n // resolver en uno: o la función llamada ya retorna Promise<Result>\n // (camino preferido: errores modelados en el dominio) o se envuelve\n // en trySafe en el sitio.\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/jsx-return-name-pascal-case\": \"error\",\n // Anti prop-drilling: ninguna prop viaja más de un nivel. Quien la\n // crea puede pasarla a UN hijo; quien la recibe no la reenvía —\n // estado y acciones a un store global o custom hook.\n \"skapxd/no-functions-inside-components\": \"error\",\n \"skapxd/no-tunnel-props\": \"error\",\n // Listeners en efectos: un AbortController por efecto, cleanup con\n // un solo abort() en vez de removeEventListener por listener.\n \"skapxd/prefer-abort-signal\": \"error\",\n \"skapxd/no-jsx-ternary-null\": \"error\",\n \"skapxd/max-hook-size\": [\n \"error\",\n {\n maxLines: 120,\n maxUseState: 1,\n },\n ],\n },\n },\n package: {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/shared/package\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/one-root-function-per-file\": \"error\",\n },\n },\n };\n}\n","// Config endurecida: ignora TODOS los comentarios `eslint-disable` (y demás\n// directivas inline) en los archivos que cubre. Así ni una persona ni un agente\n// pueden saltarse una regla con `// eslint-disable-next-line`.\nexport const strictConfig = {\n linterOptions: {\n noInlineConfig: true,\n },\n name: \"skapxd/strict\",\n};\n"],"mappings":";AAAA,OAAO,cAAc;AAEd,SAAS,6BAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA,IAGL,QAAQ,SAAS;AAAA,IACjB,eAAe;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACXO,IAAM,YAAY;AAAA,EACvB,4CAA4C;AAAA,EAC5C,6BAA6B;AAAA,EAC7B,uBAAuB;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,iCAAiC;AAAA,EACjC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,oCAAoC;AAAA,EACpC,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,sCAAsC;AAAA,EACtC,yCAAyC;AAC3C;;;ACnBA,OAAOA,eAAc;AAId,SAAS,4BAA4B;AAC1C,SAAO;AAAA,IACL,QAAQA,UAAS;AAAA,EACnB;AACF;;;ACYO,IAAM,kBAAkB;AAAA;AAAA;AAAA,EAG7B,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM1B,+BAA+B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO/B,+BAA+B;AAAA;AAAA;AAAA,EAG/B,gCAAgC;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhC,+BAA+B;AAAA,IAC7B;AAAA,IACA;AAAA,MACE,mBAAmB;AAAA,MACnB,aAAa;AAAA,MACb,cAAc;AAAA,IAChB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAKA,qCAAqC,CAAC,SAAS,MAAM;AACvD;;;ACpDO,SAAS,oBAAoB,iBAA0B;AAC5D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA,MAKN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOH,gCAAgC;AAAA,MAClC;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA,UAAU;AAAA,MACR,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA,QAKH,gCAAgC;AAAA,QAChC,sCAAsC;AAAA;AAAA;AAAA;AAAA,QAItC,yCAAyC;AAAA,QACzC,0BAA0B;AAAA;AAAA;AAAA,QAG1B,8BAA8B;AAAA,QAC9B,8BAA8B;AAAA,QAC9B,wBAAwB;AAAA,UACtB;AAAA,UACA;AAAA,YACE,UAAU;AAAA,YACV,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,qCAAqC;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACF;;;ACzEO,IAAM,eAAe;AAAA,EAC1B,eAAe;AAAA,IACb,gBAAgB;AAAA,EAClB;AAAA,EACA,MAAM;AACR;","names":["tseslint"]}
@@ -2,7 +2,7 @@ import {
2
2
  baseRules,
3
3
  createBaseLanguageOptions,
4
4
  createTypedLanguageOptions
5
- } from "./chunk-QR35MHTW.mjs";
5
+ } from "./chunk-4FQ7SFU4.mjs";
6
6
 
7
7
  // src/astro/configs.ts
8
8
  function createAstroConfigs(pluginReference) {
@@ -41,4 +41,4 @@ function createAstroConfigs(pluginReference) {
41
41
  export {
42
42
  createAstroConfigs
43
43
  };
44
- //# sourceMappingURL=chunk-C5JP4FQN.mjs.map
44
+ //# sourceMappingURL=chunk-7OIMY5TI.mjs.map