@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 +153 -11
- package/dist/astro/index.d.mts +1 -0
- package/dist/astro/index.d.ts +1 -0
- package/dist/astro/index.js +1 -0
- package/dist/astro/index.js.map +1 -1
- package/dist/astro/index.mjs +2 -2
- package/dist/{chunk-OYVXAPDZ.mjs → chunk-5BA4KA37.mjs} +64 -11
- package/dist/chunk-5BA4KA37.mjs.map +1 -0
- package/dist/{chunk-3XDQN6ZH.mjs → chunk-7H7DRTP2.mjs} +3 -2
- package/dist/{chunk-3XDQN6ZH.mjs.map → chunk-7H7DRTP2.mjs.map} +1 -1
- package/dist/{chunk-OQLKVDGZ.mjs → chunk-QJQ2E2NG.mjs} +7 -17
- package/dist/chunk-QJQ2E2NG.mjs.map +1 -0
- package/dist/{chunk-NYLQRBBK.mjs → chunk-RWQGOTVZ.mjs} +8 -8
- package/dist/chunk-RWQGOTVZ.mjs.map +1 -0
- package/dist/index.js +77 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/dist/next/index.d.mts +1 -6
- package/dist/next/index.d.ts +1 -6
- package/dist/next/index.js +5 -15
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +2 -2
- package/dist/shared/index.d.mts +1 -4
- package/dist/shared/index.d.ts +1 -4
- package/dist/shared/index.js +70 -17
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-NYLQRBBK.mjs.map +0 -1
- package/dist/chunk-OQLKVDGZ.mjs.map +0 -1
- package/dist/chunk-OYVXAPDZ.mjs.map +0 -1
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`:**
|
|
17
|
-
|
|
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`.
|
|
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` (
|
|
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
|
-
>
|
|
513
|
-
>
|
|
514
|
-
>
|
|
515
|
-
>
|
|
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
|
|
654
|
-
|
|
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
|
|
package/dist/astro/index.d.mts
CHANGED
package/dist/astro/index.d.ts
CHANGED
package/dist/astro/index.js
CHANGED
package/dist/astro/index.js.map
CHANGED
|
@@ -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"]}
|
package/dist/astro/index.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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-
|
|
1612
|
+
//# sourceMappingURL=chunk-5BA4KA37.mjs.map
|