@skapxd/eslint-opinionated 0.4.0 → 0.6.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.
package/README.md CHANGED
@@ -13,8 +13,9 @@ Cursor o Copilot.
13
13
 
14
14
  - **Una función por archivo:** un archivo con cinco helpers escondidos no pasa;
15
15
  la regla hasta te dibuja la carpeta sugerida con formato `tree`.
16
- - **Errores con `Result`:** una función `async` que puede fallar lo dice en su
17
- tipo de retorno (`Promise<Result<...>>`), no en una convención oral.
16
+ - **Errores con `Result`:** ningún `await` queda fuera del sistema de errores:
17
+ o llamas una función que retorna `Promise<Result<...>>` o envuelves la
18
+ operación en `trySafe`. Nada lanza sin que el tipo lo diga.
18
19
  - **Causa preservada:** al transformar un error de dominio, el `cause` original
19
20
  no puede desaparecer — type-aware, vía el checker de TypeScript.
20
21
  - **Hooks acotados:** un hook con demasiado estado deja de pasar como "solo un
@@ -251,6 +252,70 @@ hay errores, sale con código `1` (apto para CI). Como acota por **archivo
251
252
  completo**, también dispara las reglas estructurales (p. ej.
252
253
  `one-root-function-per-file`), que un filtrado por línea se perdería.
253
254
 
255
+ ## Cómo encaja todo: `@skapxd/result` + `ts-pattern`
256
+
257
+ Este plugin no es una colección de reglas sueltas: es el guardián de un
258
+ pipeline de errores donde cada pieza cierra un hueco que las otras dejan.
259
+
260
+ ```text
261
+ excepción ──trySafe──▶ Result ──map con cause──▶ error de dominio ──match()──▶ UI/respuesta
262
+ ```
263
+
264
+ | Pieza | Qué aporta | Regla que lo vigila |
265
+ | --- | --- | --- |
266
+ | `try/catch` prohibido | Los errores no viajan invisibles al tipo. | `skapxd/no-try-catch` |
267
+ | `.then/.catch` prohibido | Una sola forma de asincronía: `await`. | `skapxd/no-promise-chain` |
268
+ | `trySafe` (`@skapxd/result`) | La única puerta: lo que lanza se vuelve `Result`. | `skapxd/await-requires-result` |
269
+ | Errores de dominio con `cause` | Al traducir un error técnico, la causa sobrevive. | `skapxd/result-error-requires-cause` |
270
+ | Un solo contrato `Result` | Nada de `{ ok: ... }` caseros que fragmenten el sistema. | `skapxd/no-ad-hoc-ok-result` |
271
+ | `match()` (`ts-pattern`) | Consumo exhaustivo: el compilador exige manejar cada error. | `skapxd/prefer-ts-pattern` |
272
+
273
+ De punta a punta:
274
+
275
+ ```ts
276
+ import { Result, trySafe } from "@skapxd/result";
277
+ import { match } from "ts-pattern";
278
+
279
+ type UserError =
280
+ | { type: "NETWORK"; message: string; cause: unknown }
281
+ | { type: "NOT_FOUND"; message: string };
282
+
283
+ // 1. La frontera con el mundo que lanza: trySafe + errores de dominio.
284
+ async function getUser(id: string): Promise<Result<User, UserError>> {
285
+ const response = await trySafe(() => fetch(`/users/${id}`));
286
+
287
+ if (!response.ok) {
288
+ return Result.err({
289
+ cause: response.error, // result-error-requires-cause vigila esto
290
+ message: "No pude cargar el usuario.",
291
+ type: "NETWORK",
292
+ });
293
+ }
294
+
295
+ if (response.value.status === 404) {
296
+ return Result.err({ message: "El usuario no existe.", type: "NOT_FOUND" });
297
+ }
298
+
299
+ return trySafe(() => response.value.json());
300
+ }
301
+
302
+ // 2. El consumo: el await ya resuelve en Result (await-requires-result pasa)
303
+ // y match() obliga a manejar cada variante (prefer-ts-pattern).
304
+ const user = await getUser(id);
305
+
306
+ const label = match(user)
307
+ .with({ ok: true }, ({ value }) => value.name)
308
+ .with({ ok: false, error: { type: "NOT_FOUND" } }, () => "No existe")
309
+ .with({ ok: false, error: { type: "NETWORK" } }, () => "Reintenta")
310
+ .exhaustive();
311
+ ```
312
+
313
+ El resultado: ningún error puede escaparse (sin `try/catch` ni `.catch`, todo
314
+ pasa por `trySafe`), ningún error pierde su origen (siempre hay `cause` hasta
315
+ la excepción original), y ningún error queda sin manejar (el `.exhaustive()`
316
+ de ts-pattern no compila si falta una variante). Legibilidad y manejo de
317
+ errores dejan de depender de la disciplina del autor — humano o agente.
318
+
254
319
  ## Estructura del paquete
255
320
 
256
321
  ```text
@@ -302,6 +367,29 @@ export default [
302
367
  ];
303
368
  ```
304
369
 
370
+ El contrato del back es el mismo que el del front: todo `await` debe resolver
371
+ en un `Result` (`skapxd/await-requires-result`). Exigir además la firma
372
+ `Promise<Result<...>>` en cada función async
373
+ (`skapxd/async-functions-return-result`) está **apagado por defecto** — los
374
+ motivos están documentados en la sección de esa regla. Si quieres el contrato
375
+ duro, actívala encima del preset:
376
+
377
+ ```js
378
+ export default [
379
+ {
380
+ files: ["src/server/**/*.{ts,tsx}"],
381
+ ...skapxd.configs.shared.backend,
382
+ rules: {
383
+ ...skapxd.configs.shared.backend.rules,
384
+ "skapxd/async-functions-return-result": [
385
+ "error",
386
+ { checkMissingReturnType: true },
387
+ ],
388
+ },
389
+ },
390
+ ];
391
+ ```
392
+
305
393
  ### Frontend
306
394
 
307
395
  ```js
@@ -427,9 +515,9 @@ bloque con `linterOptions: { noInlineConfig: false }` para esos globs.
427
515
  | Regla | Qué protege |
428
516
  | --- | --- |
429
517
  | `skapxd/one-root-function-per-file` | Un archivo, una función top-level semántica. |
430
- | `skapxd/async-functions-return-result` | Funciones async de dominio deben retornar `Promise<Result<...>>`. |
518
+ | `skapxd/async-functions-return-result` | Funciones async de dominio deben retornar `Promise<Result<...>>`. **Apagada por defecto; opt-in** (ver motivos en su sección). |
431
519
  | `skapxd/result-error-requires-cause` | Un `Result.err` derivado debe preservar `cause: result.error`. |
432
- | `skapxd/await-requires-result` | Todo `await` debe resolver en un `Result`: o la función llamada retorna `Promise<Result<...>>` (preferido) o se envuelve en `trySafe`. La activa `shared.frontend`. |
520
+ | `skapxd/await-requires-result` | Todo `await` debe resolver en un `Result`: o la función llamada retorna `Promise<Result<...>>` (preferido) o se envuelve en `trySafe`. **Obligatoria en todos los presets tipados.** |
433
521
  | `skapxd/no-ad-hoc-ok-result` | Evita contratos `{ ok: ... }` hechos a mano en async exports. |
434
522
  | `skapxd/max-hook-size` | Marca hooks grandes o con demasiados `useState`. |
435
523
  | `skapxd/jsx-return-name-pascal-case` | Funciones que retornan JSX deben nombrarse como componentes. |
@@ -465,6 +553,39 @@ sugiere helpers al lado.
465
553
 
466
554
  ### `skapxd/async-functions-return-result`
467
555
 
556
+ > **Apagada por defecto desde v0.5.0** — ningún preset la activa. La regla
557
+ > obligatoria del sistema de errores es `skapxd/await-requires-result`.
558
+ >
559
+ > **Por qué se tomó esta decisión:**
560
+ >
561
+ > 1. **`await-requires-result` produce el mismo estado final con mejor
562
+ > ergonomía.** Si ningún `await` puede quedar sin `Result`, envolver con
563
+ > `trySafe` inline una y otra vez se vuelve incómodo rápido — la presión
564
+ > natural es extraer funciones que retornen `Promise<Result<...>>` con
565
+ > errores de dominio. Se llega a las mismas firmas que esta regla imponía,
566
+ > pero por gravedad, no por decreto.
567
+ > 2. **Imponer la firma choca con los bordes del framework.** Los handlers
568
+ > `GET/POST` de Next, `page.tsx`, los callbacks de librerías: sus firmas no
569
+ > son tuyas. Esta regla necesitaba listas de excepciones
570
+ > (`allowFilePatterns`, `allowNamePatterns`) para convivir con eso;
571
+ > `await-requires-result` no necesita ninguna, porque envolver un `await`
572
+ > es compatible con cualquier firma.
573
+ > 3. **Adopción incremental.** En un codebase existente, exigir la firma en
574
+ > cada función async lo rompe todo de golpe. Exigir `Result` en los `await`
575
+ > permite migrar llamada por llamada.
576
+ >
577
+ > Sigue disponible para quien quiera endurecer el contrato (p. ej. un backend
578
+ > nuevo donde todas las firmas son tuyas):
579
+ >
580
+ > ```js
581
+ > rules: {
582
+ > "skapxd/async-functions-return-result": ["error", {
583
+ > checkMissingReturnType: true,
584
+ > resultTypeNames: ["Result", "ResultValue", "SafeResult"],
585
+ > }],
586
+ > }
587
+ > ```
588
+
468
589
  Obliga a que funciones async en dominios configurados declaren un retorno como:
469
590
 
470
591
  ```ts
@@ -484,7 +605,8 @@ type Result<T, E> = ...; // ❌ Result ajeno
484
605
  async function no(): Promise<Result<number, Error>> {} // se reporta
485
606
  ```
486
607
 
487
- > Requiere `projectService` (los presets `backend` y `next/server` ya lo activan).
608
+ > Requiere `projectService` (actívalo en `languageOptions.parserOptions` o
609
+ > apóyate en un preset tipado del plugin, que ya lo trae).
488
610
  > Sin información de tipos cae a una comprobación por nombre (`resultTypeNames`),
489
611
  > menos estricta.
490
612
 
@@ -509,10 +631,12 @@ el archivo.
509
631
 
510
632
  ### `skapxd/await-requires-result`
511
633
 
512
- > La activa el preset `shared.frontend` en todo el front: componentes, hooks,
513
- > handlers y servicios. El contrato queda así: ninguna función está obligada
514
- > a retornar `Result`, pero todo `await` debe **resolver** en uno. Para
515
- > activarla en otros globs, añádela mismo:
634
+ > **Es la regla obligatoria del sistema de errores**: la activan todos los
635
+ > presets tipados (`shared.frontend`, `shared.backend`, `next/server`,
636
+ > `astro/typescript`). El contrato queda así: ninguna función está obligada
637
+ > a retornar `Result` (eso es `async-functions-return-result`, apagada por
638
+ > defecto), pero todo `await` debe **resolver** en uno. Para activarla en
639
+ > otros globs, añádela tú mismo:
516
640
  >
517
641
  > ```js
518
642
  > rules: {
@@ -640,6 +764,23 @@ function Card() {
640
764
  helper en minúscula **sí** pueden tener funciones dentro — ahí es donde se mueve
641
765
  la lógica.
642
766
 
767
+ **Opciones** (desde v0.6.0) para permitir los dos patrones React idiomáticos
768
+ que la versión estricta bloqueaba — forzarlos a salir del componente producía
769
+ workarounds peores que el problema (`.bind(null, ...)`, adapters artificiales):
770
+
771
+ ```js
772
+ "skapxd/no-functions-inside-components": ["error", {
773
+ allowJsxCallbacks: true, // <button onClick={() => onSelect(id)} />
774
+ allowArrayMapCallbacks: true, // {entries.map((entry) => <Entry key={entry.id} />)}
775
+ }]
776
+ ```
777
+
778
+ Ambas exenciones aplican **solo a funciones anónimas inline en esa posición
779
+ exacta**: el valor directo de una prop JSX, o el primer argumento de `.map(...)`.
780
+ Un handler con nombre en el cuerpo (`const onClick = () => ...`), un callback de
781
+ `useEffect` o un `.forEach` siguen reportándose — la lógica con peso sigue
782
+ viviendo fuera del componente.
783
+
643
784
  ### `skapxd/no-try-catch`
644
785
 
645
786
  Prohíbe `try/catch`. La intención es que los errores se modelen como `Result` en
@@ -650,8 +791,9 @@ const result = await trySafe(() => client.execute(query)); // ✅
650
791
  if (!result.ok) return Result.err({ cause: result.error, type: "DB_FAILED" });
651
792
  ```
652
793
 
653
- Se complementa con `result-error-requires-cause` (preservar la causa) y, si la
654
- activas, con `await-requires-result` (que además exige que cada `await` resuelva en un `Result`).
794
+ Se complementa con `result-error-requires-cause` (preservar la causa) y con
795
+ `await-requires-result` (obligatoria en los presets tipados: cada `await`
796
+ resuelve en un `Result`).
655
797
 
656
798
  ### `skapxd/no-promise-chain`
657
799
 
@@ -47,6 +47,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
47
47
  skapxd: unknown;
48
48
  };
49
49
  rules: {
50
+ "skapxd/await-requires-result": string;
50
51
  "skapxd/result-error-requires-cause": string;
51
52
  };
52
53
  })[];
@@ -47,6 +47,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
47
47
  skapxd: unknown;
48
48
  };
49
49
  rules: {
50
+ "skapxd/await-requires-result": string;
50
51
  "skapxd/result-error-requires-cause": string;
51
52
  };
52
53
  })[];
@@ -92,6 +92,7 @@ function createAstroConfigs(pluginReference) {
92
92
  name: "skapxd/astro/typescript",
93
93
  plugins: { skapxd: pluginReference },
94
94
  rules: {
95
+ "skapxd/await-requires-result": "error",
95
96
  "skapxd/result-error-requires-cause": "error"
96
97
  }
97
98
  }
@@ -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-typed-language-options.ts","../../src/astro/configs.ts"],"sourcesContent":["export { createAstroConfigs } from \"./configs\";\n","export const baseRules = {\n \"skapxd/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"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/result-error-requires-cause\": \"error\",\n },\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,YAAY;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACRA,+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,sCAAsC;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AACF;","names":["tseslint","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/no-ad-hoc-ok-result\": \"error\",\n \"skapxd/no-deep-relative-imports\": \"error\",\n \"skapxd/no-promise-chain\": \"error\",\n \"skapxd/no-try-catch\": \"error\",\n \"skapxd/one-root-function-per-file\": \"error\",\n \"skapxd/prefer-ts-pattern\": \"error\",\n \"skapxd/result-error-requires-cause\": \"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 },\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,YAAY;AAAA,EACvB,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACRA,+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,MACxC;AAAA,IACF;AAAA,EACF;AACF;","names":["tseslint","import_typescript_eslint","tseslint"]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAstroConfigs
3
- } from "../chunk-3XDQN6ZH.mjs";
4
- import "../chunk-NYLQRBBK.mjs";
3
+ } from "../chunk-7H7DRTP2.mjs";
4
+ import "../chunk-RWQGOTVZ.mjs";
5
5
  export {
6
6
  createAstroConfigs
7
7
  };
@@ -593,8 +593,8 @@ var asyncFunctionsReturnResult = {
593
593
  description: "Exige Promise<Result<...>> en funciones async de dominio."
594
594
  },
595
595
  messages: {
596
- missingReturnType: "La funcion async `{{name}}` debe declarar Promise<Result<...>> como tipo de retorno.",
597
- invalidReturnType: "La funcion async `{{name}}` debe retornar Promise<Result<...>> para modelar errores de forma explicita."
596
+ missingReturnType: "La funcion async `{{name}}` debe declarar Promise<Result<...>> como tipo de retorno: trySafe en la frontera, errores de dominio con `cause`, y el consumidor decide con `match()` de ts-pattern.",
597
+ invalidReturnType: "La funcion async `{{name}}` debe retornar Promise<Result<...>> para modelar errores de forma explicita: trySafe en la frontera, errores de dominio con `cause`, y el consumidor decide con `match()` de ts-pattern."
598
598
  },
599
599
  schema: [
600
600
  {
@@ -780,7 +780,7 @@ var noAdHocOkResult = {
780
780
  description: "Prohibe retornar contratos ad hoc con ok en funciones async exportadas."
781
781
  },
782
782
  messages: {
783
- adHocOkResult: "No retornes objetos ad hoc con `ok` desde la funcion async `{{name}}`. Usa Result.ok(...) / Result.err(...) con un error discriminado `{ type: ... }`."
783
+ adHocOkResult: "No retornes objetos ad hoc con `ok` desde la funcion async `{{name}}`. Usa Result.ok(...) / Result.err(...) de @skapxd/result con un error discriminado `{ type: ... }`: un unico contrato Result permite consumir cada variante con `match()` de ts-pattern y `.exhaustive()`."
784
784
  },
785
785
  schema: []
786
786
  },
@@ -903,7 +903,7 @@ var awaitRequiresResult = {
903
903
  description: "Exige que todo await resuelva en un Result: una funcion que retorne Promise<Result<...>> o trySafe en el sitio."
904
904
  },
905
905
  messages: {
906
- awaitWithoutResult: "El await dentro de `{{name}}` no resuelve en un Result. Mejor opcion: extrae la operacion a una funcion que retorne Promise<Result<...>> y modela ahi los errores de dominio (el trySafe vive dentro de esa funcion). Alternativa: envuelvela aqui mismo: `{{suggestion}}`."
906
+ awaitWithoutResult: "El await dentro de `{{name}}` no resuelve en un Result. Mejor opcion: extrae la operacion a una funcion que retorne Promise<Result<...>> y modela ahi los errores de dominio con `{ type, message, cause }` (el trySafe vive dentro de esa funcion). Alternativa: envuelvela aqui mismo: `{{suggestion}}`. En ambos casos, consume el Result con `match()` de ts-pattern."
907
907
  },
908
908
  schema: [
909
909
  {
@@ -1115,7 +1115,7 @@ var resultErrorRequiresCause = {
1115
1115
  description: "Exige preservar result.error como cause cuando una funcion que retorna Result transforma un Result fallido en Result.err."
1116
1116
  },
1117
1117
  messages: {
1118
- missingCause: "El error de `{{name}}` ya existe como `{{name}}.error`. Preservalo en Result.err con `cause: {{name}}.error`."
1118
+ missingCause: "El error de `{{name}}` ya existe como `{{name}}.error`. Preservalo en Result.err con `cause: {{name}}.error`: la cadena de causas conecta el error de dominio con la excepcion original que capturo `trySafe`; sin ella el debugging pierde el contexto."
1119
1119
  },
1120
1120
  schema: []
1121
1121
  },
@@ -1337,6 +1337,37 @@ var noDeepRelativeImports = {
1337
1337
  }
1338
1338
  };
1339
1339
 
1340
+ // src/utils/get-no-functions-inside-components-options.ts
1341
+ function getNoFunctionsInsideComponentsOptions(options = {}) {
1342
+ return {
1343
+ allowArrayMapCallbacks: options.allowArrayMapCallbacks ?? false,
1344
+ allowJsxCallbacks: options.allowJsxCallbacks ?? false
1345
+ };
1346
+ }
1347
+
1348
+ // src/utils/is-anonymous-inline-function.ts
1349
+ function isAnonymousInlineFunction(node) {
1350
+ if (node.type === "ArrowFunctionExpression") {
1351
+ return true;
1352
+ }
1353
+ return node.type === "FunctionExpression" && !node.id;
1354
+ }
1355
+
1356
+ // src/utils/is-array-map-callback.ts
1357
+ function isArrayMapCallback(node) {
1358
+ const parent = node.parent;
1359
+ if (parent?.type !== "CallExpression" || parent.arguments[0] !== node) {
1360
+ return false;
1361
+ }
1362
+ const callee = parent.callee;
1363
+ return callee?.type === "MemberExpression" && !callee.computed && callee.property?.type === "Identifier" && callee.property.name === "map";
1364
+ }
1365
+
1366
+ // src/utils/is-jsx-attribute-callback.ts
1367
+ function isJsxAttributeCallback(node) {
1368
+ return node.parent?.type === "JSXExpressionContainer" && node.parent.parent?.type === "JSXAttribute";
1369
+ }
1370
+
1340
1371
  // src/rules/no-functions-inside-components.ts
1341
1372
  var noFunctionsInsideComponents = {
1342
1373
  meta: {
@@ -1347,17 +1378,39 @@ var noFunctionsInsideComponents = {
1347
1378
  messages: {
1348
1379
  functionInsideComponent: "No definas funciones dentro del componente `{{component}}`: se recrean en cada render. Muevela a un hook (`useX`) o a un helper fuera del componente."
1349
1380
  },
1350
- schema: []
1381
+ schema: [
1382
+ {
1383
+ additionalProperties: false,
1384
+ properties: {
1385
+ allowArrayMapCallbacks: { type: "boolean" },
1386
+ allowJsxCallbacks: { type: "boolean" }
1387
+ },
1388
+ type: "object"
1389
+ }
1390
+ ]
1351
1391
  },
1352
1392
  create(context) {
1393
+ const options = getNoFunctionsInsideComponentsOptions(context.options[0]);
1353
1394
  function isComponentFunction(node) {
1354
1395
  return isFunctionNode(node) && isPascalCaseName(getFunctionName(node));
1355
1396
  }
1397
+ function isAllowedInlineCallback(node) {
1398
+ if (!isAnonymousInlineFunction(node)) {
1399
+ return false;
1400
+ }
1401
+ if (options.allowJsxCallbacks && isJsxAttributeCallback(node)) {
1402
+ return true;
1403
+ }
1404
+ return options.allowArrayMapCallbacks && isArrayMapCallback(node);
1405
+ }
1356
1406
  function reportIfInsideComponent(node) {
1357
1407
  const enclosingFunction = getContainingFunction(node);
1358
1408
  if (!enclosingFunction || !isComponentFunction(enclosingFunction)) {
1359
1409
  return;
1360
1410
  }
1411
+ if (isAllowedInlineCallback(node)) {
1412
+ return;
1413
+ }
1361
1414
  context.report({
1362
1415
  data: {
1363
1416
  component: getFunctionName(enclosingFunction)
@@ -1382,7 +1435,7 @@ var noTryCatch = {
1382
1435
  description: "Prohibe try/catch; usa trySafe de @skapxd/result para modelar el error como Result."
1383
1436
  },
1384
1437
  messages: {
1385
- noTryCatch: "Usa `trySafe` de @skapxd/result en lugar de try/catch. El error queda modelado como Result en vez de saltar como excepcion."
1438
+ noTryCatch: "Usa `trySafe` de @skapxd/result en lugar de try/catch: el error queda modelado como Result en vez de saltar como excepcion. Luego mapealo a un error de dominio `{ type, message, cause }` y consumelo con `match()` de ts-pattern."
1386
1439
  },
1387
1440
  schema: []
1388
1441
  },
@@ -1406,8 +1459,8 @@ var preferTsPattern = {
1406
1459
  description: "Prefiere match() de ts-pattern sobre switch/case y ternarios anidados."
1407
1460
  },
1408
1461
  messages: {
1409
- noSwitch: "Usa `match()` de ts-pattern en lugar de switch/case para un control de flujo exhaustivo y tipado.",
1410
- noNestedTernary: "Usa `match()` de ts-pattern en lugar de ternarios anidados; mejora la legibilidad y la exhaustividad."
1462
+ noSwitch: "Usa `match()` de ts-pattern en lugar de switch/case para un control de flujo exhaustivo y tipado. Es la pieza que cierra el sistema de errores: un Result con errores `{ type: ... }` se consume con una rama `.with()` por variante y `.exhaustive()` garantiza que ninguna quede sin manejar.",
1463
+ noNestedTernary: "Usa `match()` de ts-pattern en lugar de ternarios anidados; mejora la legibilidad y `.exhaustive()` obliga a cubrir todos los casos."
1411
1464
  },
1412
1465
  schema: []
1413
1466
  },
@@ -1483,7 +1536,7 @@ var noPromiseChain = {
1483
1536
  description: "Prohibe encadenar .then/.catch/.finally en promesas; usa await (envuelto en trySafe)."
1484
1537
  },
1485
1538
  messages: {
1486
- noPromiseChain: "No encadenes `.{{method}}()` en una promesa. La unica forma de tratar funciones asincronas es `await` (envuelto en `trySafe`)."
1539
+ noPromiseChain: "No encadenes `.{{method}}()` en una promesa. La unica forma de tratar funciones asincronas es `await`: o la funcion llamada ya retorna Promise<Result<...>> o envuelve la llamada en `trySafe` de @skapxd/result."
1487
1540
  },
1488
1541
  schema: [
1489
1542
  {
@@ -1556,4 +1609,4 @@ var rules = {
1556
1609
  export {
1557
1610
  rules
1558
1611
  };
1559
- //# sourceMappingURL=chunk-OYVXAPDZ.mjs.map
1612
+ //# sourceMappingURL=chunk-5BA4KA37.mjs.map