@skapxd/eslint-opinionated 0.9.0 → 0.11.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 +90 -1
- package/dist/astro/index.d.mts +2 -0
- package/dist/astro/index.d.ts +2 -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-5P6OHVFD.mjs → chunk-3NM7FEIN.mjs} +2 -2
- package/dist/{chunk-O4RTJGEU.mjs → chunk-EXMF54EM.mjs} +228 -24
- package/dist/chunk-EXMF54EM.mjs.map +1 -0
- package/dist/{chunk-EGNXI5HL.mjs → chunk-J472YWOD.mjs} +5 -1
- package/dist/chunk-J472YWOD.mjs.map +1 -0
- package/dist/{chunk-QRYL7RJO.mjs → chunk-Z2MYYOIS.mjs} +3 -2
- package/dist/{chunk-QRYL7RJO.mjs.map → chunk-Z2MYYOIS.mjs.map} +1 -1
- package/dist/index.js +233 -24
- 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 +5 -0
- package/dist/next/index.d.ts +5 -0
- package/dist/next/index.js +2 -0
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +2 -2
- package/dist/shared/index.d.mts +4 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.js +231 -23
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-EGNXI5HL.mjs.map +0 -1
- package/dist/chunk-O4RTJGEU.mjs.map +0 -1
- /package/dist/{chunk-5P6OHVFD.mjs.map → chunk-3NM7FEIN.mjs.map} +0 -0
package/README.md
CHANGED
|
@@ -548,8 +548,10 @@ 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) |
|
|
554
|
+
| `prefer-abort-signal` | `allowFilePatterns` (globs), `effectNames` (default `["useEffect", "useLayoutEffect"]`) |
|
|
553
555
|
|
|
554
556
|
Los `allowFilePatterns` de todas las reglas son **globs** (`*` un segmento,
|
|
555
557
|
`**` cualquier profundidad, `{a,b}` alternativas; un patrón sin prefijo
|
|
@@ -570,7 +572,9 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
|
|
|
570
572
|
| `skapxd/no-deep-relative-imports` | Limita la profundidad de los imports relativos (`../`). |
|
|
571
573
|
| `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. |
|
|
572
574
|
| `skapxd/no-emoji` | Prohíbe emojis en strings y JSX; cada sistema los renderiza distinto. Usa un icono SVG. |
|
|
575
|
+
| `skapxd/no-nested-if` | Prohíbe `if` anidados: retorno anticipado o `match()`. Menos carga cognitiva y sin puntos ciegos para las demás reglas. |
|
|
573
576
|
| `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. |
|
|
577
|
+
| `skapxd/prefer-abort-signal` | Listeners en efectos se limpian con `AbortController` (`{ signal }` + `abort()`), no con `removeEventListener`. |
|
|
574
578
|
| `skapxd/no-functions-inside-components` | Prohíbe definir funciones dentro de componentes React. |
|
|
575
579
|
| `skapxd/no-try-catch` | Prohíbe `try/catch`; usa `trySafe` de `@skapxd/result`. |
|
|
576
580
|
| `skapxd/no-promise-chain` | Prohíbe `.then/.catch/.finally`; usa `await` (+ `trySafe`). |
|
|
@@ -687,10 +691,19 @@ if (!result.ok) {
|
|
|
687
691
|
}
|
|
688
692
|
```
|
|
689
693
|
|
|
694
|
+
Reconoce todas las formas del guard de Result fallido — `!result.ok`,
|
|
695
|
+
`result.ok === false`, `result.ok !== true`, `Result.isErr(result)` y
|
|
696
|
+
`if (result.error)` — y dentro del guard exige el `cause` en todo
|
|
697
|
+
`Result.err(...)`. Un `Result.err()` **sin argumentos** también se reporta:
|
|
698
|
+
descartar el error por completo es el peor caso, no una exención. Y un `cause`
|
|
699
|
+
con otro valor (`cause: new Error(...)`) no cuenta: tiene que ser literalmente
|
|
700
|
+
el `result.error` del guard.
|
|
701
|
+
|
|
690
702
|
Esta regla es type-aware. Usa TypeScript parser services para confirmar que el
|
|
691
703
|
valor del guard y `Result.err` vienen de `@skapxd/result`. Por eso funciona con
|
|
692
704
|
aliases, re-exports y tipos inferidos, sin depender solo del nombre importado en
|
|
693
|
-
el archivo.
|
|
705
|
+
el archivo. Su punto ciego histórico —el `Result.err` escondido en un `if`
|
|
706
|
+
anidado— lo elimina `skapxd/no-nested-if` de raíz.
|
|
694
707
|
|
|
695
708
|
### `skapxd/await-requires-result`
|
|
696
709
|
|
|
@@ -846,6 +859,35 @@ Revisa imports estáticos (`import`), re-exports (`export ... from`) e imports
|
|
|
846
859
|
dinámicos (`import(...)`). El remedio habitual es un alias de ruta (`@/...`) o
|
|
847
860
|
acercar el módulo a quien lo usa.
|
|
848
861
|
|
|
862
|
+
### `skapxd/no-nested-if`
|
|
863
|
+
|
|
864
|
+
Prohíbe un `if` dentro de otro `if` (en la misma función). Cada nivel de
|
|
865
|
+
anidación suma carga cognitiva para quien lee — y además crea puntos ciegos
|
|
866
|
+
para las demás reglas: un `Result.err` dentro de un if anidado quedaba fuera
|
|
867
|
+
del alcance de `result-error-requires-cause`. Esta regla elimina la categoría
|
|
868
|
+
completa de evasión en vez de parchear cada caso.
|
|
869
|
+
|
|
870
|
+
```ts
|
|
871
|
+
// ❌ anidado: el lector mantiene dos condiciones en la cabeza
|
|
872
|
+
if (!response.ok) {
|
|
873
|
+
if (shouldReport) {
|
|
874
|
+
return Result.err({ cause: response.error, message: "...", type: "X" });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// ✅ retorno anticipado: una condición a la vez, camino feliz sin sangría
|
|
879
|
+
if (!response.ok && shouldReport) {
|
|
880
|
+
return Result.err({ cause: response.error, message: "...", type: "X" });
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// ✅ o match() si son variantes de un mismo valor
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
No cuenta como anidación: la cadena `else if` (es secuencia, no anidación), y
|
|
887
|
+
una función definida dentro del `if` (unidad cognitiva aparte). El propio
|
|
888
|
+
código de este plugin se aplanó con retorno anticipado al activar la regla —
|
|
889
|
+
cinco casos, todos quedaron más legibles.
|
|
890
|
+
|
|
849
891
|
### `skapxd/no-default-export`
|
|
850
892
|
|
|
851
893
|
Prohíbe `export default` (incluida la forma `export { x as default }`). Con
|
|
@@ -1085,6 +1127,53 @@ const label = match(status)
|
|
|
1085
1127
|
.exhaustive();
|
|
1086
1128
|
```
|
|
1087
1129
|
|
|
1130
|
+
### `skapxd/prefer-abort-signal`
|
|
1131
|
+
|
|
1132
|
+
Dentro de un `useEffect`/`useLayoutEffect`, los listeners se limpian con
|
|
1133
|
+
`AbortController`, no con `removeEventListener` manual:
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
// ❌ registro y limpieza espejados a mano
|
|
1137
|
+
useEffect(() => {
|
|
1138
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
1139
|
+
media.addEventListener("change", onSystemChange);
|
|
1140
|
+
return () => media.removeEventListener("change", onSystemChange);
|
|
1141
|
+
}, [settings]);
|
|
1142
|
+
|
|
1143
|
+
// ✅ un AbortController por efecto
|
|
1144
|
+
useEffect(() => {
|
|
1145
|
+
const controller = new AbortController();
|
|
1146
|
+
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
1147
|
+
media.addEventListener("change", onSystemChange, { signal: controller.signal });
|
|
1148
|
+
return () => controller.abort();
|
|
1149
|
+
}, [settings]);
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
Por qué: un solo `abort()` limpia **todos** los listeners del efecto (no hay
|
|
1153
|
+
que espejar cada `add` con su `remove`), y elimina el bug clásico de pasar una
|
|
1154
|
+
referencia distinta a `removeEventListener` (un `.bind()` o una arrow nueva)
|
|
1155
|
+
que deja el listener vivo para siempre.
|
|
1156
|
+
|
|
1157
|
+
Reporta dos cosas dentro del callback del efecto (incluidas sus funciones
|
|
1158
|
+
anidadas y el cleanup): `addEventListener` sin `signal` en las options, y
|
|
1159
|
+
cualquier `removeEventListener`. Fuera de un efecto la regla no opina.
|
|
1160
|
+
|
|
1161
|
+
Cuando las options no son un objeto literal, la verificación resuelve en
|
|
1162
|
+
capas:
|
|
1163
|
+
|
|
1164
|
+
1. **Por scope**: `addEventListener("x", fn, opts)` sigue `opts` hasta su
|
|
1165
|
+
`const opts = {...}` y lo inspecciona — sin necesitar type-checking.
|
|
1166
|
+
2. **Por tipo** (con `projectService`): si no hay inicializador visible (un
|
|
1167
|
+
parámetro, un import), pregunta al checker si el **tipo** declara `signal`;
|
|
1168
|
+
si ni el tipo la tiene, es imposible que llegue y se reporta.
|
|
1169
|
+
3. Sin inicializador ni tipos: beneficio de la duda.
|
|
1170
|
+
|
|
1171
|
+
El boolean de capture (`addEventListener("x", fn, true)`) se reporta siempre:
|
|
1172
|
+
no puede traer `signal`.
|
|
1173
|
+
|
|
1174
|
+
`effectNames` permite cubrir wrappers propios (`["useEffect",
|
|
1175
|
+
"useLayoutEffect", "useIsomorphicEffect"]`).
|
|
1176
|
+
|
|
1088
1177
|
### `skapxd/no-jsx-ternary-null`
|
|
1089
1178
|
|
|
1090
1179
|
Cuando renderizas JSX condicional y una rama del ternario es `null`, prefiere la
|
package/dist/astro/index.d.mts
CHANGED
|
@@ -14,6 +14,7 @@ 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;
|
|
@@ -31,6 +32,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
|
|
|
31
32
|
"skapxd/no-deep-relative-imports": string;
|
|
32
33
|
"skapxd/no-default-export": string;
|
|
33
34
|
"skapxd/no-emoji": string;
|
|
35
|
+
"skapxd/no-nested-if": string;
|
|
34
36
|
"skapxd/no-promise-chain": string;
|
|
35
37
|
"skapxd/no-try-catch": string;
|
|
36
38
|
"skapxd/one-root-function-per-file": string;
|
package/dist/astro/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ 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;
|
|
@@ -31,6 +32,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
|
|
|
31
32
|
"skapxd/no-deep-relative-imports": string;
|
|
32
33
|
"skapxd/no-default-export": string;
|
|
33
34
|
"skapxd/no-emoji": string;
|
|
35
|
+
"skapxd/no-nested-if": string;
|
|
34
36
|
"skapxd/no-promise-chain": string;
|
|
35
37
|
"skapxd/no-try-catch": string;
|
|
36
38
|
"skapxd/one-root-function-per-file": string;
|
package/dist/astro/index.js
CHANGED
|
@@ -40,6 +40,7 @@ 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",
|
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-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;;;
|
|
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};\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,uBAAuB;AAAA,EACvB,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACXA,+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
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
baseRules,
|
|
3
3
|
createBaseLanguageOptions,
|
|
4
4
|
createTypedLanguageOptions
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-J472YWOD.mjs";
|
|
6
6
|
|
|
7
7
|
// src/astro/configs.ts
|
|
8
8
|
function createAstroConfigs(pluginReference) {
|
|
@@ -40,4 +40,4 @@ function createAstroConfigs(pluginReference) {
|
|
|
40
40
|
export {
|
|
41
41
|
createAstroConfigs
|
|
42
42
|
};
|
|
43
|
-
//# sourceMappingURL=chunk-
|
|
43
|
+
//# sourceMappingURL=chunk-3NM7FEIN.mjs.map
|
|
@@ -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
|
-
|
|
818
|
-
|
|
819
|
-
return parent;
|
|
820
|
-
}
|
|
821
|
-
return null;
|
|
818
|
+
if (!isFunctionNode(currentNode)) {
|
|
819
|
+
currentNode = currentNode.parent;
|
|
820
|
+
continue;
|
|
822
821
|
}
|
|
823
|
-
|
|
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
|
|
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({
|
|
@@ -1697,6 +1710,134 @@ var noTryCatch = {
|
|
|
1697
1710
|
}
|
|
1698
1711
|
};
|
|
1699
1712
|
|
|
1713
|
+
// src/utils/get-prefer-abort-signal-options.ts
|
|
1714
|
+
function getPreferAbortSignalOptions(options = {}) {
|
|
1715
|
+
return {
|
|
1716
|
+
allowFilePatterns: options.allowFilePatterns ?? [],
|
|
1717
|
+
effectNames: options.effectNames ?? ["useEffect", "useLayoutEffect"]
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/utils/get-variable-initializer.ts
|
|
1722
|
+
function getVariableInitializer(identifier, scope) {
|
|
1723
|
+
let current = scope;
|
|
1724
|
+
while (current) {
|
|
1725
|
+
const variable = current.variables.find(
|
|
1726
|
+
(candidate) => candidate.name === identifier.name
|
|
1727
|
+
);
|
|
1728
|
+
if (variable) {
|
|
1729
|
+
const definition = variable.defs[0];
|
|
1730
|
+
return definition?.node?.type === "VariableDeclarator" ? definition.node.init : null;
|
|
1731
|
+
}
|
|
1732
|
+
current = current.upper;
|
|
1733
|
+
}
|
|
1734
|
+
return null;
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// src/utils/object-expression-has-signal.ts
|
|
1738
|
+
function objectExpressionHasSignal(objectExpression) {
|
|
1739
|
+
return objectExpression.properties.some(
|
|
1740
|
+
(property) => property.type === "SpreadElement" || isPropertyKeyNamed(property, "signal")
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
// src/utils/has-abort-signal-option.ts
|
|
1745
|
+
function hasAbortSignalOption(callExpression, sourceCode, typeContext) {
|
|
1746
|
+
const options = callExpression.arguments[2];
|
|
1747
|
+
if (!options) {
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
if (options.type === "Literal") {
|
|
1751
|
+
return false;
|
|
1752
|
+
}
|
|
1753
|
+
if (options.type === "ObjectExpression") {
|
|
1754
|
+
return objectExpressionHasSignal(options);
|
|
1755
|
+
}
|
|
1756
|
+
const initializer = options.type === "Identifier" ? getVariableInitializer(options, sourceCode.getScope(options)) : null;
|
|
1757
|
+
if (initializer?.type === "ObjectExpression") {
|
|
1758
|
+
return objectExpressionHasSignal(initializer);
|
|
1759
|
+
}
|
|
1760
|
+
if (typeContext) {
|
|
1761
|
+
const type = typeContext.services.getTypeAtLocation(options);
|
|
1762
|
+
return Boolean(typeContext.checker.getPropertyOfType(type, "signal"));
|
|
1763
|
+
}
|
|
1764
|
+
return true;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
// src/utils/is-inside-effect-callback.ts
|
|
1768
|
+
function isInsideEffectCallback(node, effectNames) {
|
|
1769
|
+
let current = node.parent;
|
|
1770
|
+
while (current) {
|
|
1771
|
+
const call = isFunctionNode(current) ? current.parent : null;
|
|
1772
|
+
if (call?.type === "CallExpression" && call.arguments[0] === current && isCalleeNamed(call.callee, effectNames)) {
|
|
1773
|
+
return true;
|
|
1774
|
+
}
|
|
1775
|
+
current = current.parent;
|
|
1776
|
+
}
|
|
1777
|
+
return false;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// src/rules/prefer-abort-signal.ts
|
|
1781
|
+
var preferAbortSignal = {
|
|
1782
|
+
meta: {
|
|
1783
|
+
type: "suggestion",
|
|
1784
|
+
docs: {
|
|
1785
|
+
description: "En efectos de React, los listeners se limpian con AbortController, no con removeEventListener."
|
|
1786
|
+
},
|
|
1787
|
+
messages: {
|
|
1788
|
+
addWithoutSignal: "Este addEventListener vive en un efecto sin `signal`. Crea `const controller = new AbortController()`, pasa `{ signal: controller.signal }` como tercer argumento y limpia con `return () => controller.abort()`: un solo abort cubre todos los listeners del efecto.",
|
|
1789
|
+
removeInsteadOfAbort: "No quites listeners a mano en el cleanup: registra con `{ signal: controller.signal }` y reemplaza este removeEventListener por `return () => controller.abort()`. Evita el bug clasico de pasar una referencia distinta al remover."
|
|
1790
|
+
},
|
|
1791
|
+
schema: [
|
|
1792
|
+
{
|
|
1793
|
+
additionalProperties: false,
|
|
1794
|
+
properties: {
|
|
1795
|
+
allowFilePatterns: {
|
|
1796
|
+
items: { type: "string" },
|
|
1797
|
+
type: "array"
|
|
1798
|
+
},
|
|
1799
|
+
effectNames: {
|
|
1800
|
+
items: { type: "string" },
|
|
1801
|
+
type: "array"
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
type: "object"
|
|
1805
|
+
}
|
|
1806
|
+
]
|
|
1807
|
+
},
|
|
1808
|
+
create(context) {
|
|
1809
|
+
const options = getPreferAbortSignalOptions(context.options[0]);
|
|
1810
|
+
const filename = context.filename ?? context.getFilename();
|
|
1811
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
1812
|
+
const typeContext = getTypeContext(context);
|
|
1813
|
+
if (matchesAnyGlob(filename, options.allowFilePatterns)) {
|
|
1814
|
+
return {};
|
|
1815
|
+
}
|
|
1816
|
+
return {
|
|
1817
|
+
CallExpression(node) {
|
|
1818
|
+
if (node.callee?.type !== "MemberExpression") {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const isAdd = isMemberPropertyNamed(node.callee, "addEventListener");
|
|
1822
|
+
const isRemove = isMemberPropertyNamed(node.callee, "removeEventListener");
|
|
1823
|
+
if (!isAdd && !isRemove) {
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
if (!isInsideEffectCallback(node, options.effectNames)) {
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
if (isRemove) {
|
|
1830
|
+
context.report({ messageId: "removeInsteadOfAbort", node });
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
if (!hasAbortSignalOption(node, sourceCode, typeContext)) {
|
|
1834
|
+
context.report({ messageId: "addWithoutSignal", node });
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
};
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1700
1841
|
// src/rules/prefer-ts-pattern.ts
|
|
1701
1842
|
var preferTsPattern = {
|
|
1702
1843
|
meta: {
|
|
@@ -1768,6 +1909,67 @@ var noJsxTernaryNull = {
|
|
|
1768
1909
|
}
|
|
1769
1910
|
};
|
|
1770
1911
|
|
|
1912
|
+
// src/utils/get-no-nested-if-options.ts
|
|
1913
|
+
function getNoNestedIfOptions(options = {}) {
|
|
1914
|
+
return {
|
|
1915
|
+
allowFilePatterns: options.allowFilePatterns ?? []
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
// src/utils/is-nested-if-statement.ts
|
|
1920
|
+
function isNestedIfStatement(node) {
|
|
1921
|
+
if (node.parent?.type === "IfStatement" && node.parent.alternate === node) {
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
let current = node.parent;
|
|
1925
|
+
while (current && !isFunctionNode(current)) {
|
|
1926
|
+
if (current.type === "IfStatement") {
|
|
1927
|
+
return true;
|
|
1928
|
+
}
|
|
1929
|
+
current = current.parent;
|
|
1930
|
+
}
|
|
1931
|
+
return false;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/rules/no-nested-if.ts
|
|
1935
|
+
var noNestedIf = {
|
|
1936
|
+
meta: {
|
|
1937
|
+
type: "suggestion",
|
|
1938
|
+
docs: {
|
|
1939
|
+
description: "Prohibe if anidados; usa retorno anticipado (guard clauses) o match() de ts-pattern."
|
|
1940
|
+
},
|
|
1941
|
+
messages: {
|
|
1942
|
+
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."
|
|
1943
|
+
},
|
|
1944
|
+
schema: [
|
|
1945
|
+
{
|
|
1946
|
+
additionalProperties: false,
|
|
1947
|
+
properties: {
|
|
1948
|
+
allowFilePatterns: {
|
|
1949
|
+
items: { type: "string" },
|
|
1950
|
+
type: "array"
|
|
1951
|
+
}
|
|
1952
|
+
},
|
|
1953
|
+
type: "object"
|
|
1954
|
+
}
|
|
1955
|
+
]
|
|
1956
|
+
},
|
|
1957
|
+
create(context) {
|
|
1958
|
+
const options = getNoNestedIfOptions(context.options[0]);
|
|
1959
|
+
const filename = context.filename ?? context.getFilename();
|
|
1960
|
+
if (matchesAnyGlob(filename, options.allowFilePatterns)) {
|
|
1961
|
+
return {};
|
|
1962
|
+
}
|
|
1963
|
+
return {
|
|
1964
|
+
IfStatement(node) {
|
|
1965
|
+
if (isNestedIfStatement(node)) {
|
|
1966
|
+
context.report({ messageId: "noNestedIf", node });
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1771
1973
|
// src/utils/is-promise-type.ts
|
|
1772
1974
|
function isPromiseType(type, typeContext) {
|
|
1773
1975
|
return typeContext.checker.getPromisedTypeOfPromise(type) !== void 0;
|
|
@@ -1810,11 +2012,11 @@ var noPromiseChain = {
|
|
|
1810
2012
|
if (!method) {
|
|
1811
2013
|
return;
|
|
1812
2014
|
}
|
|
1813
|
-
if (typeContext
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
2015
|
+
if (typeContext && !isPromiseType(
|
|
2016
|
+
typeContext.services.getTypeAtLocation(callee.object),
|
|
2017
|
+
typeContext
|
|
2018
|
+
)) {
|
|
2019
|
+
return;
|
|
1818
2020
|
}
|
|
1819
2021
|
context.report({
|
|
1820
2022
|
data: { method },
|
|
@@ -1850,12 +2052,14 @@ var rules = {
|
|
|
1850
2052
|
"no-tunnel-props": noTunnelProps,
|
|
1851
2053
|
"no-functions-inside-components": noFunctionsInsideComponents,
|
|
1852
2054
|
"no-try-catch": noTryCatch,
|
|
2055
|
+
"prefer-abort-signal": preferAbortSignal,
|
|
1853
2056
|
"prefer-ts-pattern": preferTsPattern,
|
|
1854
2057
|
"no-jsx-ternary-null": noJsxTernaryNull,
|
|
2058
|
+
"no-nested-if": noNestedIf,
|
|
1855
2059
|
"no-promise-chain": noPromiseChain
|
|
1856
2060
|
};
|
|
1857
2061
|
|
|
1858
2062
|
export {
|
|
1859
2063
|
rules
|
|
1860
2064
|
};
|
|
1861
|
-
//# sourceMappingURL=chunk-
|
|
2065
|
+
//# sourceMappingURL=chunk-EXMF54EM.mjs.map
|