@skapxd/eslint-opinionated 0.10.0 → 0.12.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
@@ -548,9 +548,11 @@ de cada regla):
548
548
  | `no-default-export` | `allowFilePatterns` (globs, aditivos a los integrados) |
549
549
  | `no-emoji` | `allowFilePatterns` (globs) |
550
550
  | `no-functions-inside-components` | `allowJsxCallbacks`, `allowArrayMapCallbacks` (ambas `true` por defecto) |
551
+ | `no-nested-if` | `allowFilePatterns` (globs) |
551
552
  | `no-promise-chain` | `methods` |
552
553
  | `no-tunnel-props` | `allowFilePatterns` (globs), `allowPropPatterns` (regex) |
553
554
  | `prefer-abort-signal` | `allowFilePatterns` (globs), `effectNames` (default `["useEffect", "useLayoutEffect"]`) |
555
+ | `result-error-requires-handling` | `allowFilePatterns` (globs) |
554
556
 
555
557
  Los `allowFilePatterns` de todas las reglas son **globs** (`*` un segmento,
556
558
  `**` cualquier profundidad, `{a,b}` alternativas; un patrón sin prefijo
@@ -564,6 +566,7 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
564
566
  | `skapxd/one-root-function-per-file` | Un archivo, una función top-level semántica. |
565
567
  | `skapxd/async-functions-return-result` | Funciones async de dominio deben retornar `Promise<Result<...>>`. **Apagada por defecto; opt-in** (ver motivos en su sección). |
566
568
  | `skapxd/result-error-requires-cause` | Un `Result.err` derivado debe preservar `cause: result.error`. |
569
+ | `skapxd/result-error-requires-handling` | Prohíbe descartar en silencio un Result fallido: el error se transforma o se entrega, nunca se ignora. |
567
570
  | `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.** |
568
571
  | `skapxd/no-ad-hoc-ok-result` | Evita contratos `{ ok: ... }` hechos a mano en async exports. |
569
572
  | `skapxd/max-hook-size` | Marca hooks grandes o con demasiados `useState`. |
@@ -571,6 +574,7 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
571
574
  | `skapxd/no-deep-relative-imports` | Limita la profundidad de los imports relativos (`../`). |
572
575
  | `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. |
573
576
  | `skapxd/no-emoji` | Prohíbe emojis en strings y JSX; cada sistema los renderiza distinto. Usa un icono SVG. |
577
+ | `skapxd/no-nested-if` | Prohíbe `if` anidados: retorno anticipado o `match()`. Menos carga cognitiva y sin puntos ciegos para las demás reglas. |
574
578
  | `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. |
575
579
  | `skapxd/prefer-abort-signal` | Listeners en efectos se limpian con `AbortController` (`{ signal }` + `abort()`), no con `removeEventListener`. |
576
580
  | `skapxd/no-functions-inside-components` | Prohíbe definir funciones dentro de componentes React. |
@@ -689,10 +693,58 @@ if (!result.ok) {
689
693
  }
690
694
  ```
691
695
 
696
+ Reconoce todas las formas del guard de Result fallido — `!result.ok`,
697
+ `result.ok === false`, `result.ok !== true`, `Result.isErr(result)` y
698
+ `if (result.error)` — y dentro del guard exige el `cause` en todo
699
+ `Result.err(...)`. Un `Result.err()` **sin argumentos** también se reporta:
700
+ descartar el error por completo es el peor caso, no una exención. Y un `cause`
701
+ con otro valor (`cause: new Error(...)`) no cuenta: tiene que ser literalmente
702
+ el `result.error` del guard.
703
+
692
704
  Esta regla es type-aware. Usa TypeScript parser services para confirmar que el
693
705
  valor del guard y `Result.err` vienen de `@skapxd/result`. Por eso funciona con
694
706
  aliases, re-exports y tipos inferidos, sin depender solo del nombre importado en
695
- el archivo.
707
+ el archivo. Su punto ciego histórico —el `Result.err` escondido en un `if`
708
+ anidado— lo elimina `skapxd/no-nested-if` de raíz.
709
+
710
+ ### `skapxd/result-error-requires-handling`
711
+
712
+ La hermana de la anterior cierra la última puerta de evasión: el **descarte
713
+ silencioso**. Detectar el fallo y botarlo sin tocarlo es legal para
714
+ `result-error-requires-cause` (no hay transformación que vigilar), pero deja
715
+ morir información valiosa sin que nadie lo decidiera conscientemente:
716
+
717
+ ```ts
718
+ const result = await copyTextToClipboard(text);
719
+ if (!result.ok) return; // ❌ el error muere aquí, en silencio
720
+ ```
721
+
722
+ El contrato: dentro de un guard de Result fallido, `result.error` (o el
723
+ result completo) debe **fluir a alguna parte**. Dos salidas:
724
+
725
+ ```ts
726
+ // 1. Transformarlo (y result-error-requires-cause vigila el cause)
727
+ if (!result.ok) {
728
+ return Result.err({ cause: result.error, message: "...", type: "COPY_FAILED" });
729
+ }
730
+
731
+ // 2. Entregárselo a alguien: telemetría, estado de error, log de dominio
732
+ if (!result.ok) {
733
+ trackClipboardFailure(result.error);
734
+ return;
735
+ }
736
+
737
+ // (propagar el result completo también vale: `if (!result.ok) return result;`)
738
+ ```
739
+
740
+ **No hay tercera salida.** `void result.error` no cuenta como manejo, y
741
+ manejar sin tocar el error (`setFailed(true)`) tampoco — el detalle se perdió
742
+ igual. Esto es deliberado: si darle seguimiento a un error es crítico o no,
743
+ no puede depender de la interpretación de quien escribe; el camino por
744
+ defecto nunca es ignorarlo.
745
+
746
+ Type-aware como su hermana: solo aplica a Results reales de `@skapxd/result`,
747
+ con las mismas cinco formas de guard.
696
748
 
697
749
  ### `skapxd/await-requires-result`
698
750
 
@@ -848,6 +900,35 @@ Revisa imports estáticos (`import`), re-exports (`export ... from`) e imports
848
900
  dinámicos (`import(...)`). El remedio habitual es un alias de ruta (`@/...`) o
849
901
  acercar el módulo a quien lo usa.
850
902
 
903
+ ### `skapxd/no-nested-if`
904
+
905
+ Prohíbe un `if` dentro de otro `if` (en la misma función). Cada nivel de
906
+ anidación suma carga cognitiva para quien lee — y además crea puntos ciegos
907
+ para las demás reglas: un `Result.err` dentro de un if anidado quedaba fuera
908
+ del alcance de `result-error-requires-cause`. Esta regla elimina la categoría
909
+ completa de evasión en vez de parchear cada caso.
910
+
911
+ ```ts
912
+ // ❌ anidado: el lector mantiene dos condiciones en la cabeza
913
+ if (!response.ok) {
914
+ if (shouldReport) {
915
+ return Result.err({ cause: response.error, message: "...", type: "X" });
916
+ }
917
+ }
918
+
919
+ // ✅ retorno anticipado: una condición a la vez, camino feliz sin sangría
920
+ if (!response.ok && shouldReport) {
921
+ return Result.err({ cause: response.error, message: "...", type: "X" });
922
+ }
923
+
924
+ // ✅ o match() si son variantes de un mismo valor
925
+ ```
926
+
927
+ No cuenta como anidación: la cadena `else if` (es secuencia, no anidación), y
928
+ una función definida dentro del `if` (unidad cognitiva aparte). El propio
929
+ código de este plugin se aplanó con retorno anticipado al activar la regla —
930
+ cinco casos, todos quedaron más legibles.
931
+
851
932
  ### `skapxd/no-default-export`
852
933
 
853
934
  Prohíbe `export default` (incluida la forma `export { x as default }`). Con
@@ -14,11 +14,13 @@ declare function createAstroConfigs(pluginReference: unknown): ({
14
14
  "skapxd/no-deep-relative-imports": string;
15
15
  "skapxd/no-default-export": string;
16
16
  "skapxd/no-emoji": string;
17
+ "skapxd/no-nested-if": string;
17
18
  "skapxd/no-promise-chain": string;
18
19
  "skapxd/no-try-catch": string;
19
20
  "skapxd/one-root-function-per-file": string;
20
21
  "skapxd/prefer-ts-pattern": string;
21
22
  "skapxd/result-error-requires-cause": string;
23
+ "skapxd/result-error-requires-handling": string;
22
24
  };
23
25
  } | {
24
26
  files: string[];
@@ -31,11 +33,13 @@ declare function createAstroConfigs(pluginReference: unknown): ({
31
33
  "skapxd/no-deep-relative-imports": string;
32
34
  "skapxd/no-default-export": string;
33
35
  "skapxd/no-emoji": string;
36
+ "skapxd/no-nested-if": string;
34
37
  "skapxd/no-promise-chain": string;
35
38
  "skapxd/no-try-catch": string;
36
39
  "skapxd/one-root-function-per-file": string;
37
40
  "skapxd/prefer-ts-pattern": string;
38
41
  "skapxd/result-error-requires-cause": string;
42
+ "skapxd/result-error-requires-handling": string;
39
43
  };
40
44
  languageOptions?: undefined;
41
45
  } | {
@@ -53,6 +57,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
53
57
  rules: {
54
58
  "skapxd/await-requires-result": string;
55
59
  "skapxd/result-error-requires-cause": string;
60
+ "skapxd/result-error-requires-handling": string;
56
61
  };
57
62
  })[];
58
63
 
@@ -14,11 +14,13 @@ declare function createAstroConfigs(pluginReference: unknown): ({
14
14
  "skapxd/no-deep-relative-imports": string;
15
15
  "skapxd/no-default-export": string;
16
16
  "skapxd/no-emoji": string;
17
+ "skapxd/no-nested-if": string;
17
18
  "skapxd/no-promise-chain": string;
18
19
  "skapxd/no-try-catch": string;
19
20
  "skapxd/one-root-function-per-file": string;
20
21
  "skapxd/prefer-ts-pattern": string;
21
22
  "skapxd/result-error-requires-cause": string;
23
+ "skapxd/result-error-requires-handling": string;
22
24
  };
23
25
  } | {
24
26
  files: string[];
@@ -31,11 +33,13 @@ declare function createAstroConfigs(pluginReference: unknown): ({
31
33
  "skapxd/no-deep-relative-imports": string;
32
34
  "skapxd/no-default-export": string;
33
35
  "skapxd/no-emoji": string;
36
+ "skapxd/no-nested-if": string;
34
37
  "skapxd/no-promise-chain": string;
35
38
  "skapxd/no-try-catch": string;
36
39
  "skapxd/one-root-function-per-file": string;
37
40
  "skapxd/prefer-ts-pattern": string;
38
41
  "skapxd/result-error-requires-cause": string;
42
+ "skapxd/result-error-requires-handling": string;
39
43
  };
40
44
  languageOptions?: undefined;
41
45
  } | {
@@ -53,6 +57,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
53
57
  rules: {
54
58
  "skapxd/await-requires-result": string;
55
59
  "skapxd/result-error-requires-cause": string;
60
+ "skapxd/result-error-requires-handling": string;
56
61
  };
57
62
  })[];
58
63
 
@@ -40,11 +40,13 @@ var baseRules = {
40
40
  "skapxd/no-deep-relative-imports": "error",
41
41
  "skapxd/no-default-export": "error",
42
42
  "skapxd/no-emoji": "error",
43
+ "skapxd/no-nested-if": "error",
43
44
  "skapxd/no-promise-chain": "error",
44
45
  "skapxd/no-try-catch": "error",
45
46
  "skapxd/one-root-function-per-file": "error",
46
47
  "skapxd/prefer-ts-pattern": "error",
47
- "skapxd/result-error-requires-cause": "error"
48
+ "skapxd/result-error-requires-cause": "error",
49
+ "skapxd/result-error-requires-handling": "error"
48
50
  };
49
51
 
50
52
  // src/shared/configs/create-base-language-options.ts
@@ -95,7 +97,8 @@ function createAstroConfigs(pluginReference) {
95
97
  plugins: { skapxd: pluginReference },
96
98
  rules: {
97
99
  "skapxd/await-requires-result": "error",
98
- "skapxd/result-error-requires-cause": "error"
100
+ "skapxd/result-error-requires-cause": "error",
101
+ "skapxd/result-error-requires-handling": "error"
99
102
  }
100
103
  }
101
104
  ];
@@ -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-default-export\": \"error\",\n \"skapxd/no-emoji\": \"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,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACVA,+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
+ {"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-default-export\": \"error\",\n \"skapxd/no-emoji\": \"error\",\n \"skapxd/no-nested-if\": \"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 \"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,8BAA8B;AAAA,EAC9B,mCAAmC;AAAA,EACnC,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AAAA,EACtC,yCAAyC;AAC3C;;;ACZA,+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-7VZBQ6FD.mjs";
4
- import "../chunk-GEVX3BTI.mjs";
3
+ } from "../chunk-JA2ZO3KQ.mjs";
4
+ import "../chunk-QADXO5IL.mjs";
5
5
  export {
6
6
  createAstroConfigs
7
7
  };
@@ -6,7 +6,7 @@ import {
6
6
  baseRules,
7
7
  createBaseLanguageOptions,
8
8
  createTypedLanguageOptions
9
- } from "./chunk-GEVX3BTI.mjs";
9
+ } from "./chunk-QADXO5IL.mjs";
10
10
 
11
11
  // src/next/configs.ts
12
12
  var nextDefaultExportFileGlob = `{${[
@@ -71,4 +71,4 @@ function createNextConfigs(pluginReference) {
71
71
  export {
72
72
  createNextConfigs
73
73
  };
74
- //# sourceMappingURL=chunk-3WLCAPUA.mjs.map
74
+ //# sourceMappingURL=chunk-DJXM5PMG.mjs.map
@@ -2,7 +2,7 @@ import {
2
2
  baseRules,
3
3
  createBaseLanguageOptions,
4
4
  createTypedLanguageOptions
5
- } from "./chunk-GEVX3BTI.mjs";
5
+ } from "./chunk-QADXO5IL.mjs";
6
6
 
7
7
  // src/astro/configs.ts
8
8
  function createAstroConfigs(pluginReference) {
@@ -31,7 +31,8 @@ function createAstroConfigs(pluginReference) {
31
31
  plugins: { skapxd: pluginReference },
32
32
  rules: {
33
33
  "skapxd/await-requires-result": "error",
34
- "skapxd/result-error-requires-cause": "error"
34
+ "skapxd/result-error-requires-cause": "error",
35
+ "skapxd/result-error-requires-handling": "error"
35
36
  }
36
37
  }
37
38
  ];
@@ -40,4 +41,4 @@ function createAstroConfigs(pluginReference) {
40
41
  export {
41
42
  createAstroConfigs
42
43
  };
43
- //# sourceMappingURL=chunk-7VZBQ6FD.mjs.map
44
+ //# sourceMappingURL=chunk-JA2ZO3KQ.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/astro/configs.ts"],"sourcesContent":["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":";;;;;;;AAMO,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":[]}
1
+ {"version":3,"sources":["../src/astro/configs.ts"],"sourcesContent":["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":";;;;;;;AAMO,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":[]}
@@ -644,14 +644,16 @@ var asyncFunctionsReturnResult = {
644
644
  return;
645
645
  }
646
646
  const returnType = node.returnType?.typeAnnotation;
647
+ const missingReturnTypeIsReportable = options.checkMissingReturnType || containsCallNamed(node.body, options.checkMissingReturnTypeWhenCallNames);
648
+ if (!returnType && missingReturnTypeIsReportable) {
649
+ context.report({
650
+ data: { name: functionName },
651
+ messageId: "missingReturnType",
652
+ node: reportNode
653
+ });
654
+ return;
655
+ }
647
656
  if (!returnType) {
648
- if (options.checkMissingReturnType || containsCallNamed(node.body, options.checkMissingReturnTypeWhenCallNames)) {
649
- context.report({
650
- data: { name: functionName },
651
- messageId: "missingReturnType",
652
- node: reportNode
653
- });
654
- }
655
657
  return;
656
658
  }
657
659
  if (isSkapxdResultReturnType(returnType)) {
@@ -813,14 +815,13 @@ function getAwaitScopeName(node) {
813
815
  function getEnclosingTrySafeCall(node, trySafeCallNames) {
814
816
  let currentNode = node.parent;
815
817
  while (currentNode) {
816
- if (isFunctionNode(currentNode)) {
817
- const parent = currentNode.parent;
818
- if (parent?.type === "CallExpression" && parent.arguments.includes(currentNode) && isCalleeNamed(parent.callee, trySafeCallNames)) {
819
- return parent;
820
- }
821
- return null;
818
+ if (!isFunctionNode(currentNode)) {
819
+ currentNode = currentNode.parent;
820
+ continue;
822
821
  }
823
- currentNode = currentNode.parent;
822
+ const parent = currentNode.parent;
823
+ const isTrySafeArgument = parent?.type === "CallExpression" && parent.arguments.includes(currentNode) && isCalleeNamed(parent.callee, trySafeCallNames);
824
+ return isTrySafeArgument ? parent : null;
824
825
  }
825
826
  return null;
826
827
  }
@@ -954,6 +955,18 @@ var awaitRequiresResult = {
954
955
  }
955
956
  };
956
957
 
958
+ // src/utils/get-error-member-object.ts
959
+ function getErrorMemberObject(node) {
960
+ const unwrappedNode = unwrapExpression(node);
961
+ if (unwrappedNode.type !== "MemberExpression" || unwrappedNode.object.type !== "Identifier" || !isMemberPropertyNamed(unwrappedNode, "error")) {
962
+ return null;
963
+ }
964
+ return {
965
+ name: unwrappedNode.object.name,
966
+ node: unwrappedNode.object
967
+ };
968
+ }
969
+
957
970
  // src/utils/get-ok-member-object.ts
958
971
  function getOkMemberObject(node) {
959
972
  const unwrappedNode = unwrapExpression(node);
@@ -1002,6 +1015,9 @@ function getResultCheckArgument(node, methodName) {
1002
1015
  // src/utils/get-failed-result-guard.ts
1003
1016
  function getFailedResultGuard(node) {
1004
1017
  const unwrappedNode = unwrapExpression(node);
1018
+ if (unwrappedNode.type === "MemberExpression") {
1019
+ return getErrorMemberObject(unwrappedNode);
1020
+ }
1005
1021
  if (unwrappedNode.type === "UnaryExpression" && unwrappedNode.operator === "!") {
1006
1022
  return getOkMemberObject(unwrappedNode.argument) ?? getResultCheckArgument(unwrappedNode.argument, "isOk");
1007
1023
  }
@@ -1119,10 +1135,7 @@ var resultErrorRequiresCause = {
1119
1135
  if (!isSkapxdResultErrCall(resultErrCall, typeContext)) {
1120
1136
  continue;
1121
1137
  }
1122
- if (resultErrCall.arguments.length === 0) {
1123
- continue;
1124
- }
1125
- if (resultErrPreservesCause(resultErrCall.arguments[0], resultGuard.name)) {
1138
+ if (resultErrCall.arguments.length > 0 && resultErrPreservesCause(resultErrCall.arguments[0], resultGuard.name)) {
1126
1139
  continue;
1127
1140
  }
1128
1141
  context.report({
@@ -1138,6 +1151,88 @@ var resultErrorRequiresCause = {
1138
1151
  }
1139
1152
  };
1140
1153
 
1154
+ // src/utils/collect-identifiers-named.ts
1155
+ function collectIdentifiersNamed(node, name, results = []) {
1156
+ if (node?.type === "Identifier" && node.name === name) {
1157
+ results.push(node);
1158
+ }
1159
+ for (const child of getNodeChildren(node)) {
1160
+ collectIdentifiersNamed(child, name, results);
1161
+ }
1162
+ return results;
1163
+ }
1164
+
1165
+ // src/utils/get-result-error-requires-handling-options.ts
1166
+ function getResultErrorRequiresHandlingOptions(options = {}) {
1167
+ return {
1168
+ allowFilePatterns: options.allowFilePatterns ?? []
1169
+ };
1170
+ }
1171
+
1172
+ // src/utils/is-consumed-result-reference.ts
1173
+ function isConsumedResultReference(identifier) {
1174
+ const member = identifier.parent?.type === "MemberExpression" && identifier.parent.object === identifier ? identifier.parent : null;
1175
+ const reference = member ?? identifier;
1176
+ const parent = reference.parent;
1177
+ if (parent?.type === "UnaryExpression" && parent.operator === "void") {
1178
+ return false;
1179
+ }
1180
+ return parent?.type !== "ExpressionStatement";
1181
+ }
1182
+
1183
+ // src/rules/result-error-requires-handling.ts
1184
+ var resultErrorRequiresHandling = {
1185
+ meta: {
1186
+ type: "problem",
1187
+ docs: {
1188
+ description: "Prohibe descartar en silencio un Result fallido: el error se transforma o se entrega, nunca se ignora."
1189
+ },
1190
+ messages: {
1191
+ unhandledResultError: "El guard detecta que `{{name}}` fallo, pero `{{name}}.error` muere aqui sin seguimiento. Transformalo (`Result.err({ cause: {{name}}.error, ... })`), entregaselo a alguien (telemetria, estado de error, log de dominio) o propaga el result completo. Si darle seguimiento es critico o no, no es una interpretacion: todo error fluye a alguna parte."
1192
+ },
1193
+ schema: [
1194
+ {
1195
+ additionalProperties: false,
1196
+ properties: {
1197
+ allowFilePatterns: {
1198
+ items: { type: "string" },
1199
+ type: "array"
1200
+ }
1201
+ },
1202
+ type: "object"
1203
+ }
1204
+ ]
1205
+ },
1206
+ create(context) {
1207
+ const options = getResultErrorRequiresHandlingOptions(context.options[0]);
1208
+ const filename = context.filename ?? context.getFilename();
1209
+ const typeContext = getTypeContext(context);
1210
+ if (matchesAnyGlob(filename, options.allowFilePatterns)) {
1211
+ return {};
1212
+ }
1213
+ return {
1214
+ IfStatement(node) {
1215
+ const resultGuard = getFailedResultGuard(node.test);
1216
+ if (!typeContext || !resultGuard || !isSkapxdResultExpression(resultGuard.node, typeContext)) {
1217
+ return;
1218
+ }
1219
+ const references = collectIdentifiersNamed(
1220
+ node.consequent,
1221
+ resultGuard.name
1222
+ );
1223
+ if (references.some(isConsumedResultReference)) {
1224
+ return;
1225
+ }
1226
+ context.report({
1227
+ data: { name: resultGuard.name },
1228
+ messageId: "unhandledResultError",
1229
+ node: node.test
1230
+ });
1231
+ }
1232
+ };
1233
+ }
1234
+ };
1235
+
1141
1236
  // src/utils/count-own-use-state-calls-in-node.ts
1142
1237
  function countOwnUseStateCallsInNode(node) {
1143
1238
  if (!isAstNode(node)) {
@@ -1448,17 +1543,6 @@ var noEmoji = {
1448
1543
  }
1449
1544
  };
1450
1545
 
1451
- // src/utils/collect-identifiers-named.ts
1452
- function collectIdentifiersNamed(node, name, results = []) {
1453
- if (node?.type === "Identifier" && node.name === name) {
1454
- results.push(node);
1455
- }
1456
- for (const child of getNodeChildren(node)) {
1457
- collectIdentifiersNamed(child, name, results);
1458
- }
1459
- return results;
1460
- }
1461
-
1462
1546
  // src/utils/get-no-tunnel-props-options.ts
1463
1547
  function getNoTunnelPropsOptions(options = {}) {
1464
1548
  return {
@@ -1740,14 +1824,9 @@ function hasAbortSignalOption(callExpression, sourceCode, typeContext) {
1740
1824
  if (options.type === "ObjectExpression") {
1741
1825
  return objectExpressionHasSignal(options);
1742
1826
  }
1743
- if (options.type === "Identifier") {
1744
- const initializer = getVariableInitializer(
1745
- options,
1746
- sourceCode.getScope(options)
1747
- );
1748
- if (initializer?.type === "ObjectExpression") {
1749
- return objectExpressionHasSignal(initializer);
1750
- }
1827
+ const initializer = options.type === "Identifier" ? getVariableInitializer(options, sourceCode.getScope(options)) : null;
1828
+ if (initializer?.type === "ObjectExpression") {
1829
+ return objectExpressionHasSignal(initializer);
1751
1830
  }
1752
1831
  if (typeContext) {
1753
1832
  const type = typeContext.services.getTypeAtLocation(options);
@@ -1760,11 +1839,9 @@ function hasAbortSignalOption(callExpression, sourceCode, typeContext) {
1760
1839
  function isInsideEffectCallback(node, effectNames) {
1761
1840
  let current = node.parent;
1762
1841
  while (current) {
1763
- if (isFunctionNode(current)) {
1764
- const call = current.parent;
1765
- if (call?.type === "CallExpression" && call.arguments[0] === current && isCalleeNamed(call.callee, effectNames)) {
1766
- return true;
1767
- }
1842
+ const call = isFunctionNode(current) ? current.parent : null;
1843
+ if (call?.type === "CallExpression" && call.arguments[0] === current && isCalleeNamed(call.callee, effectNames)) {
1844
+ return true;
1768
1845
  }
1769
1846
  current = current.parent;
1770
1847
  }
@@ -1903,6 +1980,67 @@ var noJsxTernaryNull = {
1903
1980
  }
1904
1981
  };
1905
1982
 
1983
+ // src/utils/get-no-nested-if-options.ts
1984
+ function getNoNestedIfOptions(options = {}) {
1985
+ return {
1986
+ allowFilePatterns: options.allowFilePatterns ?? []
1987
+ };
1988
+ }
1989
+
1990
+ // src/utils/is-nested-if-statement.ts
1991
+ function isNestedIfStatement(node) {
1992
+ if (node.parent?.type === "IfStatement" && node.parent.alternate === node) {
1993
+ return false;
1994
+ }
1995
+ let current = node.parent;
1996
+ while (current && !isFunctionNode(current)) {
1997
+ if (current.type === "IfStatement") {
1998
+ return true;
1999
+ }
2000
+ current = current.parent;
2001
+ }
2002
+ return false;
2003
+ }
2004
+
2005
+ // src/rules/no-nested-if.ts
2006
+ var noNestedIf = {
2007
+ meta: {
2008
+ type: "suggestion",
2009
+ docs: {
2010
+ description: "Prohibe if anidados; usa retorno anticipado (guard clauses) o match() de ts-pattern."
2011
+ },
2012
+ messages: {
2013
+ noNestedIf: "No anides un if dentro de otro: cada nivel suma carga cognitiva y crea puntos ciegos para las demas reglas. Aplana con retorno anticipado (`if (!x) return ...;` y sigue el camino feliz) o decide con `match()` de ts-pattern si son variantes de un mismo valor."
2014
+ },
2015
+ schema: [
2016
+ {
2017
+ additionalProperties: false,
2018
+ properties: {
2019
+ allowFilePatterns: {
2020
+ items: { type: "string" },
2021
+ type: "array"
2022
+ }
2023
+ },
2024
+ type: "object"
2025
+ }
2026
+ ]
2027
+ },
2028
+ create(context) {
2029
+ const options = getNoNestedIfOptions(context.options[0]);
2030
+ const filename = context.filename ?? context.getFilename();
2031
+ if (matchesAnyGlob(filename, options.allowFilePatterns)) {
2032
+ return {};
2033
+ }
2034
+ return {
2035
+ IfStatement(node) {
2036
+ if (isNestedIfStatement(node)) {
2037
+ context.report({ messageId: "noNestedIf", node });
2038
+ }
2039
+ }
2040
+ };
2041
+ }
2042
+ };
2043
+
1906
2044
  // src/utils/is-promise-type.ts
1907
2045
  function isPromiseType(type, typeContext) {
1908
2046
  return typeContext.checker.getPromisedTypeOfPromise(type) !== void 0;
@@ -1945,11 +2083,11 @@ var noPromiseChain = {
1945
2083
  if (!method) {
1946
2084
  return;
1947
2085
  }
1948
- if (typeContext) {
1949
- const type = typeContext.services.getTypeAtLocation(callee.object);
1950
- if (!isPromiseType(type, typeContext)) {
1951
- return;
1952
- }
2086
+ if (typeContext && !isPromiseType(
2087
+ typeContext.services.getTypeAtLocation(callee.object),
2088
+ typeContext
2089
+ )) {
2090
+ return;
1953
2091
  }
1954
2092
  context.report({
1955
2093
  data: { method },
@@ -1978,6 +2116,7 @@ var rules = {
1978
2116
  }
1979
2117
  },
1980
2118
  "result-error-requires-cause": resultErrorRequiresCause,
2119
+ "result-error-requires-handling": resultErrorRequiresHandling,
1981
2120
  "max-hook-size": maxHookSize,
1982
2121
  "no-deep-relative-imports": noDeepRelativeImports,
1983
2122
  "no-default-export": noDefaultExport,
@@ -1988,10 +2127,11 @@ var rules = {
1988
2127
  "prefer-abort-signal": preferAbortSignal,
1989
2128
  "prefer-ts-pattern": preferTsPattern,
1990
2129
  "no-jsx-ternary-null": noJsxTernaryNull,
2130
+ "no-nested-if": noNestedIf,
1991
2131
  "no-promise-chain": noPromiseChain
1992
2132
  };
1993
2133
 
1994
2134
  export {
1995
2135
  rules
1996
2136
  };
1997
- //# sourceMappingURL=chunk-WO7EUISC.mjs.map
2137
+ //# sourceMappingURL=chunk-MSOA2V5B.mjs.map