@skapxd/eslint-opinionated 0.16.0 → 0.18.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
@@ -748,6 +748,19 @@ export default [
748
748
  ];
749
749
  ```
750
750
 
751
+ Para librerías npm escritas en TypeScript (tsup o equivalente). Trae las
752
+ bases completas + el set type-driven (tipado, con `projectService`) +
753
+ `await-requires-result` + el contrato de empaquetado:
754
+
755
+ - `skapxd/package-requires-typed-exports` — los `exports` del package.json
756
+ cablean los tipos **por condición** (`import` → `.d.mts`, `require` →
757
+ `.d.ts`); el `types` único por subpath es el bug "FalseCJS".
758
+ - `skapxd/untrusted-module-requires-adapter` — inerte hasta que declares tu
759
+ inventario de paquetes con tipos mentirosos (ver su sección).
760
+
761
+ **Este mismo repo se lintea con este preset** — dogfood: la regla de exports
762
+ nos obligó a corregir nuestro propio package.json al nacer.
763
+
751
764
  ### Strict (sin escape via `eslint-disable`)
752
765
 
753
766
  Un prompt o un agente puede saltarse cualquier regla con
@@ -818,6 +831,7 @@ de cada regla):
818
831
  | `nest-validation-pipe-config` | `allowFilePatterns` (globs), `requiredPipeOptions` (default `["transform", "whitelist"]`) |
819
832
  | `no-deep-relative-imports` | `maxDepth` |
820
833
  | `no-default-export` | `allowFilePatterns` (globs, aditivos a los integrados) |
834
+ | `no-anonymous-condition` | `allowFilePatterns` (globs), `maxMemberDepth` (default `2`), `allowTypePredicates` (default `true`, type-aware) |
821
835
  | `no-else` | `allowFilePatterns` (globs) |
822
836
  | `no-emoji` | `allowFilePatterns` (globs) |
823
837
  | `no-explicit-any` | las de la regla original de typescript-eslint (`fixToUnknown`, ...) |
@@ -831,7 +845,9 @@ de cada regla):
831
845
  | `no-runtime-state-guard` | `allowFilePatterns` (globs) |
832
846
  | `no-tunnel-props` | `allowFilePatterns` (globs), `allowPropPatterns` (regex) |
833
847
  | `prefer-abort-signal` | `allowFilePatterns` (globs), `effectNames` (default `["useEffect", "useLayoutEffect"]`) |
848
+ | `package-requires-typed-exports` | `allowFilePatterns` (globs), `anchorFilePatterns` (default `src/index.ts(x)`, `src/main.ts`) |
834
849
  | `prefer-tagged-union-state` | `allowFilePatterns` (globs), `loadingPatterns` (regex, en minúsculas), `errorPatterns` (regex, en minúsculas) |
850
+ | `untrusted-module-requires-adapter` | `modules` (default `[]` — inerte), `adapterFilePatterns` (globs), `allowFilePatterns` (globs) |
835
851
  | `requires-strict-tsconfig` | `allowFilePatterns` (globs), `anchorFilePatterns` (globs), `requiredCompilerOptions` |
836
852
  | `result-error-requires-handling` | `allowFilePatterns` (globs) |
837
853
 
@@ -864,6 +880,7 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
864
880
  | `skapxd/nest-no-swagger-in-controllers` | Los controllers no se llenan de decoradores de swagger; el plugin introspecciona los DTOs. Preset `nest`. |
865
881
  | `skapxd/nest-requires-swagger-plugin` | `nest-cli.json` debe tener el plugin `@nestjs/swagger`: la premisa de las reglas de swagger, verificada. Preset `nest`. |
866
882
  | `skapxd/nest-validation-pipe-config` | Todo `new ValidationPipe` configura `transform` y `whitelist`: la premisa de las reglas de DTOs. Preset `nest`. |
883
+ | `skapxd/no-anonymous-condition` | El `if` solo acepta condiciones ya nombradas; todo cómputo (llamada, comparación, `&&`/`||`) se extrae a una `const` con nombre semántico. **Opt-in: no está en ningún preset.** |
867
884
  | `skapxd/no-deep-relative-imports` | Limita la profundidad de los imports relativos (`../`). |
868
885
  | `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. |
869
886
  | `skapxd/no-else` | Prohíbe `else`/`else if`: el else es el estado sin nombre. Retorno anticipado, ternario simple o `match()`. |
@@ -883,6 +900,8 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
883
900
  | `skapxd/no-try-catch` | Prohíbe `try/catch`; usa `trySafe` de `@skapxd/result`. |
884
901
  | `skapxd/no-promise-chain` | Prohíbe `.then/.catch/.finally`; usa `await` (+ `trySafe`). |
885
902
  | `skapxd/prefer-ts-pattern` | Prohíbe `switch` y ternarios anidados; usa `match()` de ts-pattern. |
903
+ | `skapxd/package-requires-typed-exports` | Los `exports` del package.json declaran `types` por condición (`import` → `.d.mts`, `require` → `.d.ts`): mata el bug FalseCJS. Preset `package`. |
904
+ | `skapxd/untrusted-module-requires-adapter` | Los paquetes con tipos mentirosos (@types desfasados) solo se importan desde su adaptador: la mentira vive en UN archivo. Preset `package`. |
886
905
  | `skapxd/no-jsx-ternary-null` | Prefiere `cond && <El />` sobre `cond ? <El /> : null` en JSX. |
887
906
 
888
907
  ### `skapxd/one-root-function-per-file`
@@ -1643,6 +1662,57 @@ por indirección tampoco escapa. Solo aplica a clases con `@Controller`
1643
1662
  (configurable con `controllerDecoratorNames` para decoradores propios); los
1644
1663
  services retornan Result con orgullo — ese es el dominio.
1645
1664
 
1665
+ ### `skapxd/no-anonymous-condition`
1666
+
1667
+ La hermana de `no-else`: esa nombra los **caminos**, esta nombra la
1668
+ **pregunta**. Un `if` cuya condición es un cómputo evalúa un valor anónimo
1669
+ cuyo significado vive solo en la cabeza de quien lo escribió; la regla exige
1670
+ bautizarlo (el refactor "introduce explaining variable" de Fowler, como ley):
1671
+
1672
+ ```ts
1673
+ if (matchesAnyGlob(filename, options.allowFilePatterns)) { ... } // ❌ ¿qué significa que matchee?
1674
+
1675
+ const esArchivoExento = matchesAnyGlob(filename, options.allowFilePatterns);
1676
+ if (esArchivoExento) { ... } // ✅ la decisión se lee como prosa
1677
+ ```
1678
+
1679
+ Lo **ya nombrado** no se extrae (la lista blanca — extraerlo sería ceremonia
1680
+ sin información):
1681
+
1682
+ - Variables y sus negaciones: `isReady`, `!isReady`, `!!isReady`.
1683
+ - Accesos a propiedad hasta `maxMemberDepth` saltos (contando puntos desde
1684
+ la base como nivel 0: `result.ok` → 1, `options.rules.flag` → 2; default
1685
+ `2`) y sus negaciones — incluido el encadenamiento opcional (`config?.flag`).
1686
+ - Comparaciones contra literal booleano o nullish (`x.ok === false`,
1687
+ `x == null`, `x !== undefined`): la escritura explícita de la
1688
+ afirmación/negación/presencia. Cubre las formas oficiales del guard de
1689
+ Result, que `result-error-requires-cause/handling` necesitan ver intactas.
1690
+ - **Type guards demostrados por la firma** (`allowTypePredicates`, default
1691
+ `true`): `if (isFunctionNode(x))` pasa cuando la firma declara
1692
+ `x is FunctionNode` — el type-checker lo demuestra (evidencia, no
1693
+ convención de nombre: una `isX(...)` que devuelve `boolean` a secas sí se
1694
+ extrae). Requiere type info; sin parser services no hay evidencia y toda
1695
+ llamada exige nombre. `Result.isErr(x)` pasa por esta vía: es un type
1696
+ predicate real.
1697
+
1698
+ Lo que **sí dispara**: llamadas, comparaciones (`a.length <= b.max`,
1699
+ `status === "ready"`), combinaciones `&&`/`||` y aritmética
1700
+ (`if (total % 2)`). La extracción directa a `const` conserva el narrowing
1701
+ (TS 4.4+, aliased conditions).
1702
+
1703
+ **Opt-in deliberado: no está en ningún preset.** La calibración contra 4
1704
+ proyectos reales (2026-06-12) midió 473/95/308 hallazgos en tres backends
1705
+ NestJS en producción y 44 en un front pequeño — señal genuina en la muestra
1706
+ revisada, pero un orden de magnitud más invasiva que cualquier regla de las
1707
+ bases. Actívala por proyecto (o por carpeta, estilo ola 3 del playbook de
1708
+ adopción):
1709
+
1710
+ ```js
1711
+ rules: {
1712
+ "skapxd/no-anonymous-condition": "error",
1713
+ }
1714
+ ```
1715
+
1646
1716
  ### `skapxd/no-deep-relative-imports`
1647
1717
 
1648
1718
  Limita cuántos niveles puede subir un import relativo. Por defecto **prohíbe
@@ -2229,6 +2299,68 @@ no puede traer `signal`.
2229
2299
  `effectNames` permite cubrir wrappers propios (`["useEffect",
2230
2300
  "useLayoutEffect", "useIsomorphicEffect"]`).
2231
2301
 
2302
+ ### `skapxd/package-requires-typed-exports`
2303
+
2304
+ El contrato de empaquetado de una librería TypeScript dual (ESM + CJS): cada
2305
+ condición del mapa `exports` declara **sus propios tipos**, del sabor
2306
+ correcto.
2307
+
2308
+ ```jsonc
2309
+ "exports": {
2310
+ ".": {
2311
+ "import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
2312
+ "require": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
2313
+ }
2314
+ }
2315
+ ```
2316
+
2317
+ El antipatrón que mata es el **"FalseCJS"** (el hallazgo #1 de
2318
+ [arethetypeswrong](https://arethetypeswrong.github.io)): un `types` único por
2319
+ subpath apuntando al `.d.ts` — los consumidores ESM con
2320
+ `moduleResolution: node16` reciben tipos CJS y el contrato miente en la
2321
+ frontera más pública que tiene una librería. tsup con `dts: true` ya genera
2322
+ los dos sabores (`.d.mts` y `.d.ts`); esta regla verifica que el package.json
2323
+ de verdad los cablee y que los archivos existan en disco. Anclada al
2324
+ entrypoint (`src/index.ts` por defecto): un reporte por paquete.
2325
+
2326
+ Dogfood: esta regla nació reportando a este mismo repo — nuestros `exports`
2327
+ tenían el bug y el lint no volvió a verde hasta corregirlos.
2328
+
2329
+ ### `skapxd/untrusted-module-requires-adapter`
2330
+
2331
+ ¿Qué pasa cuando los tipos de un paquete de terceros **mienten**? El clásico:
2332
+ un paquete escrito en JS cuyos tipos viven aparte (`@types/...`) y van
2333
+ desfasados del runtime real, o índices que juran nunca devolver `undefined`.
2334
+ Todo el sistema de este paquete descansa en que el tipo dice la verdad
2335
+ (`no-impossible-branch` le cree ciegamente) — un tipo mentiroso envenena cada
2336
+ regla type-aware que lo toque.
2337
+
2338
+ El playbook, en orden:
2339
+
2340
+ 1. **Armadura de tsconfig primero**: `noUncheckedIndexedAccess` corrige de
2341
+ raíz la clase más común de mentira (index signatures optimistas) sin
2342
+ tocar al tercero — `requires-strict-tsconfig` ya lo exige.
2343
+ 2. **Frontera anticorrupción** (lo que esta regla impone): declara el módulo
2344
+ como no confiable y enciérralo tras UN adaptador. El adaptador importa el
2345
+ paquete, re-declara los tipos honestos (lo que el runtime de verdad
2346
+ devuelve) y exporta esa versión. El resto del código importa el adaptador
2347
+ y razona con tipos veraces — la mentira queda en un archivo auditable.
2348
+ 3. **`@ts-expect-error` con descripción** dentro del adaptador si hace falta
2349
+ forzar la corrección — es la puerta que `no-silenced-compiler` deja
2350
+ abierta, declarada y con porqué.
2351
+ 4. **Arregla el upstream**: PR a DefinitelyTyped. Mientras llega, los pasos
2352
+ 1-3 te protegen.
2353
+
2354
+ ```js
2355
+ "skapxd/untrusted-module-requires-adapter": ["error", {
2356
+ adapterFilePatterns: ["src/lib/xlsx-adapter.ts"],
2357
+ modules: ["xlsx"],
2358
+ }]
2359
+ ```
2360
+
2361
+ Sin `modules` declarados la regla es inerte: el inventario de sospechosos es
2362
+ una decisión del proyecto, no una adivinanza del linter (axioma A5).
2363
+
2232
2364
  ### `skapxd/no-jsx-ternary-null`
2233
2365
 
2234
2366
  Cuando renderizas JSX condicional y una rama del ternario es `null`, prefiere la
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createAstroConfigs
3
- } from "../chunk-7OIMY5TI.mjs";
4
- import "../chunk-4FQ7SFU4.mjs";
3
+ } from "../chunk-GSQWHSHV.mjs";
4
+ import "../chunk-QDQUU6QK.mjs";
5
5
  export {
6
6
  createAstroConfigs
7
7
  };
@@ -2,7 +2,7 @@ import {
2
2
  baseRules,
3
3
  createTypedLanguageOptions,
4
4
  typeDrivenRules
5
- } from "./chunk-4FQ7SFU4.mjs";
5
+ } from "./chunk-QDQUU6QK.mjs";
6
6
 
7
7
  // src/constants/nest-entrypoint-file-patterns.ts
8
8
  var nestEntrypointFilePatterns = [
@@ -130,4 +130,4 @@ function createNestConfigs(pluginReference) {
130
130
  export {
131
131
  createNestConfigs
132
132
  };
133
- //# sourceMappingURL=chunk-LSLLVT64.mjs.map
133
+ //# sourceMappingURL=chunk-3GEFHNU7.mjs.map
@@ -6,7 +6,7 @@ import {
6
6
  baseRules,
7
7
  createBaseLanguageOptions,
8
8
  createTypedLanguageOptions
9
- } from "./chunk-4FQ7SFU4.mjs";
9
+ } from "./chunk-QDQUU6QK.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-YRWX3POD.mjs.map
74
+ //# sourceMappingURL=chunk-54EOEKYS.mjs.map
@@ -2652,6 +2652,157 @@ var noDefaultExport = {
2652
2652
  }
2653
2653
  };
2654
2654
 
2655
+ // src/utils/call-has-type-predicate.ts
2656
+ function callHasTypePredicate(node, parserServices) {
2657
+ const tsNode = parserServices?.esTreeNodeToTSNodeMap?.get(node);
2658
+ const program = parserServices?.program;
2659
+ if (tsNode === void 0 || program === null || program === void 0) {
2660
+ return false;
2661
+ }
2662
+ const checker = program.getTypeChecker();
2663
+ const signature = checker.getResolvedSignature(
2664
+ tsNode
2665
+ );
2666
+ if (signature === void 0) {
2667
+ return false;
2668
+ }
2669
+ return checker.getTypePredicateOfSignature(signature) !== void 0;
2670
+ }
2671
+
2672
+ // src/utils/get-member-chain-depth.ts
2673
+ function getMemberChainDepth(node) {
2674
+ let current = node.type === "ChainExpression" && node.expression !== void 0 ? node.expression : node;
2675
+ let depth = 0;
2676
+ while (current.type === "MemberExpression") {
2677
+ depth += 1;
2678
+ if (current.object === void 0) {
2679
+ return null;
2680
+ }
2681
+ current = current.object;
2682
+ }
2683
+ if (current.type === "Identifier" || current.type === "ThisExpression") {
2684
+ return depth;
2685
+ }
2686
+ return null;
2687
+ }
2688
+
2689
+ // src/utils/get-no-anonymous-condition-options.ts
2690
+ function getNoAnonymousConditionOptions(options = {}) {
2691
+ return {
2692
+ allowFilePatterns: options.allowFilePatterns ?? [],
2693
+ // Permitir llamadas cuya firma el type-checker demuestra como type
2694
+ // predicate (`x is T`). Sin type info, no hay evidencia y no aplica.
2695
+ allowTypePredicates: options.allowTypePredicates ?? true,
2696
+ // Saltos de propiedad permitidos en la lista blanca, contando desde la
2697
+ // base como nivel 0: `result.ok` → 1, `options.rules.flag` → 2.
2698
+ maxMemberDepth: options.maxMemberDepth ?? 2
2699
+ };
2700
+ }
2701
+
2702
+ // src/utils/is-guard-literal.ts
2703
+ function isGuardLiteral(node) {
2704
+ if (node.type === "Literal") {
2705
+ return typeof node.value === "boolean" || node.value === null;
2706
+ }
2707
+ return node.type === "Identifier" && node.name === "undefined";
2708
+ }
2709
+
2710
+ // src/utils/is-literal-guard-comparison.ts
2711
+ var guardOperators = /* @__PURE__ */ new Set(["===", "!==", "==", "!="]);
2712
+ function isLiteralGuardComparison(node, maxMemberDepth) {
2713
+ if (node.type !== "BinaryExpression") {
2714
+ return false;
2715
+ }
2716
+ if (node.operator === void 0 || !guardOperators.has(node.operator)) {
2717
+ return false;
2718
+ }
2719
+ const { left, right } = node;
2720
+ if (left === void 0 || right === void 0) {
2721
+ return false;
2722
+ }
2723
+ const leftIsGuard = isGuardLiteral(left);
2724
+ const rightIsGuard = isGuardLiteral(right);
2725
+ if (!leftIsGuard && !rightIsGuard) {
2726
+ return false;
2727
+ }
2728
+ const namedSide = leftIsGuard ? right : left;
2729
+ const depth = getMemberChainDepth(namedSide);
2730
+ return depth !== null && depth <= maxMemberDepth;
2731
+ }
2732
+
2733
+ // src/utils/unwrap-negations.ts
2734
+ function unwrapNegations(node) {
2735
+ let current = node;
2736
+ while (current.type === "UnaryExpression" && current.operator === "!" && current.argument !== void 0) {
2737
+ current = current.argument;
2738
+ }
2739
+ return current;
2740
+ }
2741
+
2742
+ // src/rules/no-anonymous-condition.ts
2743
+ var noAnonymousCondition = {
2744
+ meta: {
2745
+ type: "suggestion",
2746
+ docs: {
2747
+ description: "La condicion sin nombre: el if solo acepta condiciones ya nombradas (variables, accesos acotados, type guards demostrados); todo computo se extrae a una const con nombre semantico."
2748
+ },
2749
+ messages: {
2750
+ anonymousCondition: "La condicion sin nombre: este if evalua un computo cuyo significado el lector debe deducir. Extraelo a una const con nombre semantico y la decision se lee como prosa: `const esArchivoExento = matchesAnyGlob(archivo, patrones); if (esArchivoExento) ...`. La extraccion directa a const conserva el narrowing (TS 4.4+). Lo ya-nombrado no se extrae: variables (`isReady`), accesos como `result.ok` (hasta {{maxMemberDepth}} saltos), sus negaciones (`!result.ok`), comparaciones con literal booleano o nullish (`x.ok === false`, `x == null`) y type guards que la firma demuestra (`x is T`)."
2751
+ },
2752
+ schema: [
2753
+ {
2754
+ additionalProperties: false,
2755
+ properties: {
2756
+ allowFilePatterns: {
2757
+ items: { type: "string" },
2758
+ type: "array"
2759
+ },
2760
+ allowTypePredicates: {
2761
+ type: "boolean"
2762
+ },
2763
+ maxMemberDepth: {
2764
+ type: "number"
2765
+ }
2766
+ },
2767
+ type: "object"
2768
+ }
2769
+ ]
2770
+ },
2771
+ create(context) {
2772
+ const options = getNoAnonymousConditionOptions(
2773
+ context.options[0]
2774
+ );
2775
+ const filename = context.filename ?? context.getFilename?.() ?? "";
2776
+ if (matchesAnyGlob(filename, options.allowFilePatterns)) {
2777
+ return {};
2778
+ }
2779
+ return {
2780
+ IfStatement(node) {
2781
+ const condition = unwrapNegations(node.test);
2782
+ if (condition.type === "Literal") {
2783
+ return;
2784
+ }
2785
+ const depth = getMemberChainDepth(condition);
2786
+ if (depth !== null && depth <= options.maxMemberDepth) {
2787
+ return;
2788
+ }
2789
+ if (isLiteralGuardComparison(condition, options.maxMemberDepth)) {
2790
+ return;
2791
+ }
2792
+ const isProvenTypeGuard = condition.type === "CallExpression" && options.allowTypePredicates && callHasTypePredicate(condition, context.sourceCode?.parserServices);
2793
+ if (isProvenTypeGuard) {
2794
+ return;
2795
+ }
2796
+ context.report({
2797
+ data: { maxMemberDepth: String(options.maxMemberDepth) },
2798
+ messageId: "anonymousCondition",
2799
+ node: node.test
2800
+ });
2801
+ }
2802
+ };
2803
+ }
2804
+ };
2805
+
2655
2806
  // src/utils/get-no-else-options.ts
2656
2807
  function getNoElseOptions(options = {}) {
2657
2808
  return {
@@ -2761,8 +2912,14 @@ var noEmoji = {
2761
2912
 
2762
2913
  // src/utils/wrap-tseslint-rule.ts
2763
2914
  import tseslint from "typescript-eslint";
2915
+ var upstreamRules = tseslint.plugin.rules;
2764
2916
  function wrapTseslintRule(upstreamRuleName, { description, messages }) {
2765
- const original = tseslint.plugin.rules[upstreamRuleName];
2917
+ const original = upstreamRules[upstreamRuleName];
2918
+ if (!original) {
2919
+ throw new Error(
2920
+ `wrapTseslintRule: la regla "${upstreamRuleName}" no existe en typescript-eslint.`
2921
+ );
2922
+ }
2766
2923
  return {
2767
2924
  ...original,
2768
2925
  meta: {
@@ -2856,7 +3013,10 @@ function getNoTunnelPropsOptions(options = {}) {
2856
3013
 
2857
3014
  // src/utils/get-object-pattern-prop-names.ts
2858
3015
  function getObjectPatternPropNames(pattern) {
2859
- const result = { propNames: [], restName: null };
3016
+ const result = {
3017
+ propNames: [],
3018
+ restName: null
3019
+ };
2860
3020
  if (pattern?.type !== "ObjectPattern") {
2861
3021
  return result;
2862
3022
  }
@@ -3494,7 +3654,7 @@ function readResolvedTsconfig(tsconfigPath) {
3494
3654
  if (!parsed.ok || !parsed.value) {
3495
3655
  return null;
3496
3656
  }
3497
- return parsed.value.options ?? null;
3657
+ return parsed.value.options;
3498
3658
  }
3499
3659
 
3500
3660
  // src/rules/requires-strict-tsconfig.ts
@@ -3841,6 +4001,212 @@ var noRuntimeStateGuard = {
3841
4001
  }
3842
4002
  };
3843
4003
 
4004
+ // src/rules/package-requires-typed-exports.ts
4005
+ import { readFileSync as readFileSync2 } from "fs";
4006
+ import { dirname as dirname6, resolve as resolve3 } from "path";
4007
+ import { trySafe as trySafe4 } from "@skapxd/result";
4008
+
4009
+ // src/utils/get-typed-exports-options.ts
4010
+ function getTypedExportsOptions(options = {}) {
4011
+ return {
4012
+ allowFilePatterns: options.allowFilePatterns ?? [],
4013
+ // Anclada al entrypoint de la libreria: un reporte por paquete.
4014
+ anchorFilePatterns: options.anchorFilePatterns ?? [
4015
+ "src/index.ts",
4016
+ "src/index.tsx",
4017
+ "src/main.ts"
4018
+ ]
4019
+ };
4020
+ }
4021
+
4022
+ // src/utils/get-untyped-export-conditions.ts
4023
+ import { existsSync as existsSync3 } from "fs";
4024
+ import { join as join3 } from "path";
4025
+ function getUntypedExportConditions(exportsField, packageDir) {
4026
+ const violations = [];
4027
+ const entries = "import" in exportsField || "require" in exportsField ? [[".", exportsField]] : Object.entries(exportsField);
4028
+ for (const [subpath, value] of entries) {
4029
+ if (subpath === "./package.json") {
4030
+ continue;
4031
+ }
4032
+ if (typeof value === "string") {
4033
+ violations.push({ kind: "untyped", condition: "todas", subpath });
4034
+ continue;
4035
+ }
4036
+ for (const condition of ["import", "require"]) {
4037
+ if (!(condition in value)) {
4038
+ continue;
4039
+ }
4040
+ const target = value[condition];
4041
+ if (typeof target !== "object" || typeof target.types !== "string") {
4042
+ violations.push({ kind: "untyped", condition, subpath });
4043
+ continue;
4044
+ }
4045
+ const expectsEsmTypes = condition === "import";
4046
+ const flavorOk = expectsEsmTypes ? target.types.endsWith(".d.mts") : target.types.endsWith(".d.ts") || target.types.endsWith(".d.cts");
4047
+ if (!flavorOk) {
4048
+ violations.push({ kind: "wrong-flavor", condition, subpath });
4049
+ continue;
4050
+ }
4051
+ if (!existsSync3(join3(packageDir, target.types))) {
4052
+ violations.push({ kind: "missing-file", condition, subpath });
4053
+ }
4054
+ }
4055
+ }
4056
+ return violations;
4057
+ }
4058
+
4059
+ // src/rules/package-requires-typed-exports.ts
4060
+ var kindMessages = {
4061
+ "missing-file": "missingTypesFile",
4062
+ untyped: "untypedCondition",
4063
+ "wrong-flavor": "wrongTypesFlavor"
4064
+ };
4065
+ var packageRequiresTypedExports = {
4066
+ meta: {
4067
+ type: "problem",
4068
+ docs: {
4069
+ description: "El package.json de una libreria debe cablear los tipos POR CONDICION en exports: import \u2192 .d.mts, require \u2192 .d.ts. Un types unico es el bug FalseCJS."
4070
+ },
4071
+ messages: {
4072
+ missingExports: "El package.json no tiene campo `exports`. Sin el, Node y TypeScript resuelven por heuristicas viejas y los consumidores ESM/CJS pueden recibir artefactos cruzados. Declara el mapa de exports con `types` por condicion.",
4073
+ missingTypesFile: 'En exports["{{subpath}}"].{{condition}}, el archivo de `types` declarado no existe en disco. El contrato apunta a un fantasma: o falta el build (dts) o la ruta esta mal escrita.',
4074
+ unreadablePackageJson: "No encontre un package.json legible subiendo desde este archivo. Esta regla valida el contrato de tipos de la libreria y necesita leerlo.",
4075
+ untypedCondition: 'exports["{{subpath}}"] \u2192 `{{condition}}` no declara su propio `types`. Un `types` unico a nivel del subpath es el bug "FalseCJS": el consumidor ESM con moduleResolution node16 recibe los tipos CJS. Cada condicion declara los suyos \u2014 `import` como objeto con `types: ./dist/x.d.mts` y `default: ./dist/x.mjs`; `require` como objeto con `types: ./dist/x.d.ts` y `default: ./dist/x.js`.',
4076
+ wrongTypesFlavor: 'exports["{{subpath}}"].{{condition}} apunta a tipos del formato equivocado: `import` exige `.d.mts` (tipos ESM) y `require` exige `.d.ts`/`.d.cts` (tipos CJS). tsup con dts: true ya genera ambos sabores.'
4077
+ },
4078
+ schema: [
4079
+ {
4080
+ additionalProperties: false,
4081
+ properties: {
4082
+ allowFilePatterns: {
4083
+ items: { type: "string" },
4084
+ type: "array"
4085
+ },
4086
+ anchorFilePatterns: {
4087
+ items: { type: "string" },
4088
+ type: "array"
4089
+ }
4090
+ },
4091
+ type: "object"
4092
+ }
4093
+ ]
4094
+ },
4095
+ create(context) {
4096
+ const options = getTypedExportsOptions(context.options[0]);
4097
+ const filename = context.filename ?? context.getFilename();
4098
+ if (matchesAnyGlob(filename, options.allowFilePatterns) || !matchesAnyGlob(filename, options.anchorFilePatterns)) {
4099
+ return {};
4100
+ }
4101
+ return {
4102
+ Program(node) {
4103
+ const absoluteFilename = resolve3(context.cwd ?? process.cwd(), filename);
4104
+ const packageJsonPath = findProjectFile(
4105
+ dirname6(absoluteFilename),
4106
+ "package.json"
4107
+ );
4108
+ const parsed = packageJsonPath ? trySafe4(() => JSON.parse(readFileSync2(packageJsonPath, "utf8"))) : null;
4109
+ if (!parsed || !parsed.ok) {
4110
+ context.report({ messageId: "unreadablePackageJson", node });
4111
+ return;
4112
+ }
4113
+ const exportsField = parsed.value.exports;
4114
+ if (!exportsField || typeof exportsField !== "object") {
4115
+ context.report({ messageId: "missingExports", node });
4116
+ return;
4117
+ }
4118
+ const violations = getUntypedExportConditions(
4119
+ exportsField,
4120
+ dirname6(packageJsonPath)
4121
+ );
4122
+ for (const violation of violations) {
4123
+ context.report({
4124
+ data: {
4125
+ condition: violation.condition,
4126
+ subpath: violation.subpath
4127
+ },
4128
+ messageId: kindMessages[violation.kind],
4129
+ node
4130
+ });
4131
+ }
4132
+ }
4133
+ };
4134
+ }
4135
+ };
4136
+
4137
+ // src/utils/get-untrusted-module-options.ts
4138
+ function getUntrustedModuleOptions(options = {}) {
4139
+ return {
4140
+ // Globs de los archivos adaptador: el UNICO lugar desde donde se permite
4141
+ // importar los modulos declarados como no confiables.
4142
+ adapterFilePatterns: options.adapterFilePatterns ?? [],
4143
+ allowFilePatterns: options.allowFilePatterns ?? [],
4144
+ // Inventario de paquetes cuyos tipos mienten (p. ej. @types desfasados).
4145
+ // Vacio por defecto: la regla es inerte hasta que el proyecto declara
4146
+ // sus sospechosos.
4147
+ modules: options.modules ?? []
4148
+ };
4149
+ }
4150
+
4151
+ // src/rules/untrusted-module-requires-adapter.ts
4152
+ var untrustedModuleRequiresAdapter = {
4153
+ meta: {
4154
+ type: "problem",
4155
+ docs: {
4156
+ description: "Los modulos declarados como no confiables (tipos que mienten) solo se importan desde su adaptador: la mentira vive en un archivo, no en todos."
4157
+ },
4158
+ messages: {
4159
+ untrustedImport: "`{{moduleSource}}` esta declarado como modulo de tipos NO confiables: importalo solo desde su adaptador ({{adapters}}). El adaptador es la frontera anticorrupcion: re-declara ahi los tipos honestos (lo que el runtime de verdad devuelve) y exporta esa version; el resto del codigo importa el adaptador y razona con tipos veraces. Asi la mentira de terceros vive en UN archivo auditable, no regada por el proyecto."
4160
+ },
4161
+ schema: [
4162
+ {
4163
+ additionalProperties: false,
4164
+ properties: {
4165
+ adapterFilePatterns: {
4166
+ items: { type: "string" },
4167
+ type: "array"
4168
+ },
4169
+ allowFilePatterns: {
4170
+ items: { type: "string" },
4171
+ type: "array"
4172
+ },
4173
+ modules: {
4174
+ items: { type: "string" },
4175
+ type: "array"
4176
+ }
4177
+ },
4178
+ type: "object"
4179
+ }
4180
+ ]
4181
+ },
4182
+ create(context) {
4183
+ const options = getUntrustedModuleOptions(context.options[0]);
4184
+ const filename = context.filename ?? context.getFilename();
4185
+ if (options.modules.length === 0 || matchesAnyGlob(filename, options.allowFilePatterns) || matchesAnyGlob(filename, options.adapterFilePatterns)) {
4186
+ return {};
4187
+ }
4188
+ return {
4189
+ ImportDeclaration(node) {
4190
+ const source = node.source.value;
4191
+ const isUntrusted = options.modules.some(
4192
+ (moduleName) => source === moduleName || source.startsWith(`${moduleName}/`)
4193
+ );
4194
+ if (!isUntrusted) {
4195
+ return;
4196
+ }
4197
+ context.report({
4198
+ data: {
4199
+ adapters: options.adapterFilePatterns.join(", ") || "sin definir",
4200
+ moduleSource: source
4201
+ },
4202
+ messageId: "untrustedImport",
4203
+ node
4204
+ });
4205
+ }
4206
+ };
4207
+ }
4208
+ };
4209
+
3844
4210
  // src/shared/rules.ts
3845
4211
  var rules = {
3846
4212
  "class-properties-require-readonly": classPropertiesRequireReadonly,
@@ -3871,6 +4237,7 @@ var rules = {
3871
4237
  "nest-no-swagger-in-controllers": nestNoSwaggerInControllers,
3872
4238
  "nest-requires-swagger-plugin": nestRequiresSwaggerPlugin,
3873
4239
  "nest-validation-pipe-config": nestValidationPipeConfig,
4240
+ "no-anonymous-condition": noAnonymousCondition,
3874
4241
  "no-deep-relative-imports": noDeepRelativeImports,
3875
4242
  "no-default-export": noDefaultExport,
3876
4243
  "no-else": noElse,
@@ -3893,10 +4260,12 @@ var rules = {
3893
4260
  "no-jsx-ternary-null": noJsxTernaryNull,
3894
4261
  "no-nested-if": noNestedIf,
3895
4262
  "no-promise-chain": noPromiseChain,
3896
- "no-runtime-state-guard": noRuntimeStateGuard
4263
+ "no-runtime-state-guard": noRuntimeStateGuard,
4264
+ "package-requires-typed-exports": packageRequiresTypedExports,
4265
+ "untrusted-module-requires-adapter": untrustedModuleRequiresAdapter
3897
4266
  };
3898
4267
 
3899
4268
  export {
3900
4269
  rules
3901
4270
  };
3902
- //# sourceMappingURL=chunk-X2DU5BAB.mjs.map
4271
+ //# sourceMappingURL=chunk-77A4SFGD.mjs.map