@skapxd/eslint-opinionated 0.7.0 → 0.9.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
@@ -546,8 +546,10 @@ de cada regla):
546
546
  | `max-hook-size` | `maxLines`, `maxUseState` |
547
547
  | `no-deep-relative-imports` | `maxDepth` |
548
548
  | `no-default-export` | `allowFilePatterns` (globs, aditivos a los integrados) |
549
+ | `no-emoji` | `allowFilePatterns` (globs) |
549
550
  | `no-functions-inside-components` | `allowJsxCallbacks`, `allowArrayMapCallbacks` (ambas `true` por defecto) |
550
551
  | `no-promise-chain` | `methods` |
552
+ | `no-tunnel-props` | `allowFilePatterns` (globs), `allowPropPatterns` (regex) |
551
553
 
552
554
  Los `allowFilePatterns` de todas las reglas son **globs** (`*` un segmento,
553
555
  `**` cualquier profundidad, `{a,b}` alternativas; un patrón sin prefijo
@@ -567,6 +569,8 @@ matchea en cualquier carpeta). Las 7 reglas restantes no tienen opciones: su
567
569
  | `skapxd/jsx-return-name-pascal-case` | Funciones que retornan JSX deben nombrarse como componentes. |
568
570
  | `skapxd/no-deep-relative-imports` | Limita la profundidad de los imports relativos (`../`). |
569
571
  | `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
+ | `skapxd/no-emoji` | Prohíbe emojis en strings y JSX; cada sistema los renderiza distinto. Usa un icono SVG. |
573
+ | `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. |
570
574
  | `skapxd/no-functions-inside-components` | Prohíbe definir funciones dentro de componentes React. |
571
575
  | `skapxd/no-try-catch` | Prohíbe `try/catch`; usa `trySafe` de `@skapxd/result`. |
572
576
  | `skapxd/no-promise-chain` | Prohíbe `.then/.catch/.finally`; usa `await` (+ `trySafe`). |
@@ -884,6 +888,84 @@ al default export, basta mapear el named en el import dinámico:
884
888
  const Card = lazy(() => import("./card").then((m) => ({ default: m.Card })));
885
889
  ```
886
890
 
891
+ ### `skapxd/no-emoji`
892
+
893
+ Prohíbe emojis en strings, template literals y texto JSX. El problema no es
894
+ estético: un emoji se renderiza con la fuente de emojis del **sistema del
895
+ usuario** — Segoe UI Emoji en Windows, Apple Color Emoji en macOS, Noto en
896
+ Android — así que el mismo carácter se ve distinto en cada plataforma, y en
897
+ un Linux sin fuente de emojis directamente no se renderiza (sale el cuadro
898
+ vacío □). Un SVG se ve idéntico en todas partes.
899
+
900
+ ```tsx
901
+ <button>Enviar 🚀</button> // ❌ depende de la fuente del sistema
902
+ <button>Enviar <Rocket /></button> // ✅ lucide-react: idéntico en todas partes
903
+ ```
904
+
905
+ Detecta por propiedad Unicode (`Extended_Pictographic`), así que los símbolos
906
+ tipográficos normales no se tocan: `→`, `✓`, `©`, `·` pasan sin problema.
907
+
908
+ No revisa comentarios: un emoji en un comentario no llega al navegador. Para
909
+ eximir archivos completos (fixtures, seeds), usa `allowFilePatterns`:
910
+
911
+ ```js
912
+ "skapxd/no-emoji": ["error", {
913
+ allowFilePatterns: ["tests/fixtures/**"],
914
+ }]
915
+ ```
916
+
917
+ ### `skapxd/no-tunnel-props`
918
+
919
+ **Ninguna prop viaja más de un nivel.** El contrato de saltos: quien **crea**
920
+ un valor (estado de un hook, acción de un store, dato calculado) puede pasarlo
921
+ a UN hijo; quien lo **recibe** como prop no puede reenviarlo a otro
922
+ componente. Eso prohíbe exactamente la cadena `abuelo → padre → hijo` — el
923
+ prop drilling — sin tocar el paso legítimo de un nivel.
924
+
925
+ ```tsx
926
+ // ✅ primer salto: el abuelo CREA la acción y la baja un nivel
927
+ const Abuelo = () => {
928
+ const onSelect = useTranscriptStore((s) => s.select);
929
+ return <Padre onSelect={onSelect} />;
930
+ };
931
+
932
+ // ❌ segundo salto: el padre la RECIBE y la reenvía
933
+ const Padre = ({ onSelect }) => <Hijo onSelect={onSelect} />;
934
+
935
+ // ❌ el rename no lo esconde, y usarla localmente no autoriza el reenvío
936
+ const Padre = ({ onSelect }) => <Hijo handler={onSelect} />;
937
+
938
+ // ❌ el túnel puro
939
+ const Padre = ({ ...props }) => <Hijo {...props} />;
940
+ ```
941
+
942
+ La detección es local y exacta: si el identifier que pones en una prop de otro
943
+ componente viene de tus **props destructuradas**, no lo creaste tú — es su
944
+ segundo salto.
945
+
946
+ Las salidas que sugiere el mensaje:
947
+
948
+ 1. **Store global o custom hook**: la acción/estado vive en un store (p. ej.
949
+ [zustand](https://github.com/pmndrs/zustand)) o un hook, y el componente
950
+ que la necesita la consume directo — la cadena desaparece:
951
+
952
+ ```tsx
953
+ function Hijo({ entry }: { entry: Entry }) {
954
+ const select = useTranscriptStore((s) => s.select);
955
+ return <button onClick={() => select(entry.id)}>…</button>;
956
+ }
957
+ ```
958
+
959
+ 2. **Composición**: el padre arma el JSX y el intermedio recibe `children` —
960
+ el dato viaja dentro del JSX, no por props. (`children` nunca cuenta como
961
+ túnel: es la alternativa.)
962
+
963
+ No cuenta como reenvío: usar la prop (`<h2>{title}</h2>`), derivar datos
964
+ (`title={game.title}`), o pasarla a un elemento **nativo** (`value={value}`
965
+ en un `<input>` es la frontera con el DOM). Para wrappers legítimos de un
966
+ design system, exime props por nombre (`allowPropPatterns: ["^className$"]`)
967
+ o archivos completos (`allowFilePatterns`).
968
+
887
969
  ### `skapxd/no-functions-inside-components`
888
970
 
889
971
  Prohíbe definir funciones **con peso propio** dentro de un componente React
@@ -13,6 +13,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
13
13
  "skapxd/no-ad-hoc-ok-result": string;
14
14
  "skapxd/no-deep-relative-imports": string;
15
15
  "skapxd/no-default-export": string;
16
+ "skapxd/no-emoji": string;
16
17
  "skapxd/no-promise-chain": string;
17
18
  "skapxd/no-try-catch": string;
18
19
  "skapxd/one-root-function-per-file": string;
@@ -29,6 +30,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
29
30
  "skapxd/no-ad-hoc-ok-result": string;
30
31
  "skapxd/no-deep-relative-imports": string;
31
32
  "skapxd/no-default-export": string;
33
+ "skapxd/no-emoji": string;
32
34
  "skapxd/no-promise-chain": string;
33
35
  "skapxd/no-try-catch": string;
34
36
  "skapxd/one-root-function-per-file": string;
@@ -13,6 +13,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
13
13
  "skapxd/no-ad-hoc-ok-result": string;
14
14
  "skapxd/no-deep-relative-imports": string;
15
15
  "skapxd/no-default-export": string;
16
+ "skapxd/no-emoji": string;
16
17
  "skapxd/no-promise-chain": string;
17
18
  "skapxd/no-try-catch": string;
18
19
  "skapxd/one-root-function-per-file": string;
@@ -29,6 +30,7 @@ declare function createAstroConfigs(pluginReference: unknown): ({
29
30
  "skapxd/no-ad-hoc-ok-result": string;
30
31
  "skapxd/no-deep-relative-imports": string;
31
32
  "skapxd/no-default-export": string;
33
+ "skapxd/no-emoji": string;
32
34
  "skapxd/no-promise-chain": string;
33
35
  "skapxd/no-try-catch": string;
34
36
  "skapxd/one-root-function-per-file": string;
@@ -39,6 +39,7 @@ var baseRules = {
39
39
  "skapxd/no-ad-hoc-ok-result": "error",
40
40
  "skapxd/no-deep-relative-imports": "error",
41
41
  "skapxd/no-default-export": "error",
42
+ "skapxd/no-emoji": "error",
42
43
  "skapxd/no-promise-chain": "error",
43
44
  "skapxd/no-try-catch": "error",
44
45
  "skapxd/one-root-function-per-file": "error",
@@ -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-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,2BAA2B;AAAA,EAC3B,uBAAuB;AAAA,EACvB,qCAAqC;AAAA,EACrC,4BAA4B;AAAA,EAC5B,sCAAsC;AACxC;;;ACTA,+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-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,7 +1,7 @@
1
1
  import {
2
2
  createAstroConfigs
3
- } from "../chunk-OVPAO7Q3.mjs";
4
- import "../chunk-LDAR6XT6.mjs";
3
+ } from "../chunk-5P6OHVFD.mjs";
4
+ import "../chunk-EGNXI5HL.mjs";
5
5
  export {
6
6
  createAstroConfigs
7
7
  };
@@ -2,7 +2,7 @@ import {
2
2
  baseRules,
3
3
  createBaseLanguageOptions,
4
4
  createTypedLanguageOptions
5
- } from "./chunk-LDAR6XT6.mjs";
5
+ } from "./chunk-EGNXI5HL.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-OVPAO7Q3.mjs.map
43
+ //# sourceMappingURL=chunk-5P6OHVFD.mjs.map
@@ -16,6 +16,7 @@ var baseRules = {
16
16
  "skapxd/no-ad-hoc-ok-result": "error",
17
17
  "skapxd/no-deep-relative-imports": "error",
18
18
  "skapxd/no-default-export": "error",
19
+ "skapxd/no-emoji": "error",
19
20
  "skapxd/no-promise-chain": "error",
20
21
  "skapxd/no-try-catch": "error",
21
22
  "skapxd/one-root-function-per-file": "error",
@@ -69,7 +70,11 @@ function createSharedConfigs(pluginReference) {
69
70
  // en trySafe en el sitio.
70
71
  "skapxd/await-requires-result": "error",
71
72
  "skapxd/jsx-return-name-pascal-case": "error",
73
+ // Anti prop-drilling: ninguna prop viaja más de un nivel. Quien la
74
+ // crea puede pasarla a UN hijo; quien la recibe no la reenvía —
75
+ // estado y acciones a un store global o custom hook.
72
76
  "skapxd/no-functions-inside-components": "error",
77
+ "skapxd/no-tunnel-props": "error",
73
78
  "skapxd/no-jsx-ternary-null": "error",
74
79
  "skapxd/max-hook-size": [
75
80
  "error",
@@ -106,4 +111,4 @@ export {
106
111
  createSharedConfigs,
107
112
  strictConfig
108
113
  };
109
- //# sourceMappingURL=chunk-LDAR6XT6.mjs.map
114
+ //# sourceMappingURL=chunk-EGNXI5HL.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/configs/create-typed-language-options.ts","../src/shared/configs/base-rules.ts","../src/shared/configs/create-base-language-options.ts","../src/shared/configs/create-shared-configs.ts","../src/shared/configs/strict-config.ts"],"sourcesContent":["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","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 { baseRules } from \"./base-rules\";\nimport { createBaseLanguageOptions } from \"./create-base-language-options\";\nimport { createTypedLanguageOptions } from \"./create-typed-language-options\";\n\nexport function createSharedConfigs(pluginReference: unknown) {\n const baseLanguageOptions = createBaseLanguageOptions();\n const typedLanguageOptions = createTypedLanguageOptions();\n\n return {\n backend: {\n languageOptions: typedLanguageOptions,\n name: \"skapxd/shared/backend\",\n plugins: { skapxd: pluginReference },\n rules: {\n ...baseRules,\n // La regla obligatoria del sistema de errores es await-requires-result\n // (todo await resuelve en Result). async-functions-return-result queda\n // apagada por defecto: exigir la firma por decreto choca con los bordes\n // del framework y bloquea la adopción incremental; la presión sobre los\n // awaits produce el mismo estado final. Ver \"¿Por qué está apagada por\n // defecto?\" en el README.\n \"skapxd/await-requires-result\": \"error\",\n },\n },\n base: {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/shared/base\",\n plugins: { skapxd: pluginReference },\n rules: baseRules,\n },\n frontend: {\n languageOptions: typedLanguageOptions,\n name: \"skapxd/shared/frontend\",\n plugins: { skapxd: pluginReference },\n rules: {\n ...baseRules,\n // En el front no se obliga a retornar Result, pero todo await debe\n // resolver en uno: o la función llamada ya retorna Promise<Result>\n // (camino preferido: errores modelados en el dominio) o se envuelve\n // en trySafe en el sitio.\n \"skapxd/await-requires-result\": \"error\",\n \"skapxd/jsx-return-name-pascal-case\": \"error\",\n // Anti prop-drilling: ninguna prop viaja más de un nivel. Quien la\n // crea puede pasarla a UN hijo; quien la recibe no la reenvía —\n // estado y acciones a un store global o custom hook.\n \"skapxd/no-functions-inside-components\": \"error\",\n \"skapxd/no-tunnel-props\": \"error\",\n \"skapxd/no-jsx-ternary-null\": \"error\",\n \"skapxd/max-hook-size\": [\n \"error\",\n {\n maxLines: 120,\n maxUseState: 1,\n },\n ],\n },\n },\n package: {\n languageOptions: baseLanguageOptions,\n name: \"skapxd/shared/package\",\n plugins: { skapxd: pluginReference },\n rules: {\n \"skapxd/one-root-function-per-file\": \"error\",\n },\n },\n };\n}\n","// Config endurecida: ignora TODOS los comentarios `eslint-disable` (y demás\n// directivas inline) en los archivos que cubre. Así ni una persona ni un agente\n// pueden saltarse una regla con `// eslint-disable-next-line`.\nexport const strictConfig = {\n linterOptions: {\n noInlineConfig: true,\n },\n name: \"skapxd/strict\",\n};\n"],"mappings":";AAAA,OAAO,cAAc;AAEd,SAAS,6BAA6B;AAC3C,SAAO;AAAA;AAAA;AAAA,IAGL,QAAQ,SAAS;AAAA,IACjB,eAAe;AAAA,MACb,gBAAgB;AAAA,IAClB;AAAA,EACF;AACF;;;ACXO,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,OAAOA,eAAc;AAId,SAAS,4BAA4B;AAC1C,SAAO;AAAA,IACL,QAAQA,UAAS;AAAA,EACnB;AACF;;;ACJO,SAAS,oBAAoB,iBAA0B;AAC5D,QAAM,sBAAsB,0BAA0B;AACtD,QAAM,uBAAuB,2BAA2B;AAExD,SAAO;AAAA,IACL,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOH,gCAAgC;AAAA,MAClC;AAAA,IACF;AAAA,IACA,MAAM;AAAA,MACJ,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,IACT;AAAA,IACA,UAAU;AAAA,MACR,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA,QAKH,gCAAgC;AAAA,QAChC,sCAAsC;AAAA;AAAA;AAAA;AAAA,QAItC,yCAAyC;AAAA,QACzC,0BAA0B;AAAA,QAC1B,8BAA8B;AAAA,QAC9B,wBAAwB;AAAA,UACtB;AAAA,UACA;AAAA,YACE,UAAU;AAAA,YACV,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,MAAM;AAAA,MACN,SAAS,EAAE,QAAQ,gBAAgB;AAAA,MACnC,OAAO;AAAA,QACL,qCAAqC;AAAA,MACvC;AAAA,IACF;AAAA,EACF;AACF;;;AC/DO,IAAM,eAAe;AAAA,EAC1B,eAAe;AAAA,IACb,gBAAgB;AAAA,EAClB;AAAA,EACA,MAAM;AACR;","names":["tseslint"]}
@@ -1387,6 +1387,205 @@ var noDefaultExport = {
1387
1387
  }
1388
1388
  };
1389
1389
 
1390
+ // src/utils/contains-emoji.ts
1391
+ var emojiPattern = /\p{Extended_Pictographic}/u;
1392
+ function containsEmoji(value) {
1393
+ return emojiPattern.test(value);
1394
+ }
1395
+
1396
+ // src/utils/get-no-emoji-options.ts
1397
+ function getNoEmojiOptions(options = {}) {
1398
+ return {
1399
+ allowFilePatterns: options.allowFilePatterns ?? []
1400
+ };
1401
+ }
1402
+
1403
+ // src/rules/no-emoji.ts
1404
+ var noEmoji = {
1405
+ meta: {
1406
+ type: "problem",
1407
+ docs: {
1408
+ description: "Prohibe emojis en strings y JSX; cada sistema los renderiza distinto (o no los renderiza)."
1409
+ },
1410
+ messages: {
1411
+ noEmoji: "No uses emojis en el codigo: se renderizan con la fuente del sistema del usuario, distinta en cada plataforma (y ausente en algunos Linux, donde sale un cuadro vacio). Reemplazalo por un icono SVG con el mismo significado (p. ej. lucide-react) o un asset SVG propio."
1412
+ },
1413
+ schema: [
1414
+ {
1415
+ additionalProperties: false,
1416
+ properties: {
1417
+ allowFilePatterns: {
1418
+ items: { type: "string" },
1419
+ type: "array"
1420
+ }
1421
+ },
1422
+ type: "object"
1423
+ }
1424
+ ]
1425
+ },
1426
+ create(context) {
1427
+ const options = getNoEmojiOptions(context.options[0]);
1428
+ const filename = context.filename ?? context.getFilename();
1429
+ if (matchesAnyGlob(filename, options.allowFilePatterns)) {
1430
+ return {};
1431
+ }
1432
+ function reportIfEmoji(node, value) {
1433
+ if (typeof value === "string" && containsEmoji(value)) {
1434
+ context.report({ messageId: "noEmoji", node });
1435
+ }
1436
+ }
1437
+ return {
1438
+ JSXText(node) {
1439
+ reportIfEmoji(node, node.value);
1440
+ },
1441
+ Literal(node) {
1442
+ reportIfEmoji(node, node.value);
1443
+ },
1444
+ TemplateElement(node) {
1445
+ reportIfEmoji(node, node.value.raw);
1446
+ }
1447
+ };
1448
+ }
1449
+ };
1450
+
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
+ // src/utils/get-no-tunnel-props-options.ts
1463
+ function getNoTunnelPropsOptions(options = {}) {
1464
+ return {
1465
+ allowFilePatterns: options.allowFilePatterns ?? [],
1466
+ // Regex de nombres de prop que sí pueden reenviarse (p. ej.
1467
+ // ["^className$", "^style$"] en wrappers de un design system).
1468
+ allowPropPatterns: options.allowPropPatterns ?? []
1469
+ };
1470
+ }
1471
+
1472
+ // src/utils/get-object-pattern-prop-names.ts
1473
+ function getObjectPatternPropNames(pattern) {
1474
+ const result = { propNames: [], restName: null };
1475
+ if (pattern?.type !== "ObjectPattern") {
1476
+ return result;
1477
+ }
1478
+ for (const property of pattern.properties) {
1479
+ if (property.type === "RestElement" && property.argument.type === "Identifier") {
1480
+ result.restName = property.argument.name;
1481
+ continue;
1482
+ }
1483
+ if (property.type === "Property" && property.key.type === "Identifier" && property.value.type === "Identifier" && property.key.name === property.value.name) {
1484
+ result.propNames.push(property.key.name);
1485
+ }
1486
+ }
1487
+ return result;
1488
+ }
1489
+
1490
+ // src/utils/is-pascal-case-jsx-element.ts
1491
+ function isPascalCaseJsxElement(openingElement) {
1492
+ return openingElement?.type === "JSXOpeningElement" && openingElement.name?.type === "JSXIdentifier" && isPascalCaseName(openingElement.name.name);
1493
+ }
1494
+
1495
+ // src/utils/is-forwarded-prop-reference.ts
1496
+ function isForwardedPropReference(identifier) {
1497
+ const container = identifier.parent;
1498
+ if (container?.type !== "JSXExpressionContainer") {
1499
+ return false;
1500
+ }
1501
+ const attribute = container.parent;
1502
+ if (attribute?.type !== "JSXAttribute") {
1503
+ return false;
1504
+ }
1505
+ return isPascalCaseJsxElement(attribute.parent);
1506
+ }
1507
+
1508
+ // src/rules/no-tunnel-props.ts
1509
+ var noTunnelProps = {
1510
+ meta: {
1511
+ type: "problem",
1512
+ docs: {
1513
+ description: "Ninguna prop viaja mas de un nivel: quien la recibe no puede reenviarla a otro componente."
1514
+ },
1515
+ messages: {
1516
+ forwardedProp: "La prop `{{prop}}` que `{{component}}` recibe se reenvia a otro componente: ya va por su segundo salto (abuelo -> padre -> hijo). Quien CREA un valor puede pasarlo UN nivel; quien lo recibe no lo reenvia. Mueve el estado/accion a un store global (p. ej. zustand) o a un custom hook y consumelo donde se usa, o deja que el padre componga el JSX (`children`).",
1517
+ spreadTunnel: "`{{component}}` reenvia TODAS sus props con `{...{{name}}}` a otro componente: es un tunel puro. Mueve el estado a un store global (p. ej. zustand) o a un custom hook, o deja que el padre componga el JSX (`children`)."
1518
+ },
1519
+ schema: [
1520
+ {
1521
+ additionalProperties: false,
1522
+ properties: {
1523
+ allowFilePatterns: {
1524
+ items: { type: "string" },
1525
+ type: "array"
1526
+ },
1527
+ allowPropPatterns: {
1528
+ items: { type: "string" },
1529
+ type: "array"
1530
+ }
1531
+ },
1532
+ type: "object"
1533
+ }
1534
+ ]
1535
+ },
1536
+ create(context) {
1537
+ const options = getNoTunnelPropsOptions(context.options[0]);
1538
+ const filename = context.filename ?? context.getFilename();
1539
+ if (matchesAnyGlob(filename, options.allowFilePatterns)) {
1540
+ return {};
1541
+ }
1542
+ function reportSpreadTunnel(node, componentName, restName) {
1543
+ const spreads = collectIdentifiersNamed(node.body, restName).filter(
1544
+ (identifier) => identifier.parent?.type === "JSXSpreadAttribute" && isPascalCaseJsxElement(identifier.parent.parent)
1545
+ );
1546
+ if (spreads.length > 0) {
1547
+ context.report({
1548
+ data: { component: componentName, name: restName },
1549
+ messageId: "spreadTunnel",
1550
+ node: spreads[0].parent
1551
+ });
1552
+ }
1553
+ }
1554
+ function reportForwardedProps(node, componentName, propNames) {
1555
+ for (const propName of propNames) {
1556
+ if (matchesAnyPattern(propName, options.allowPropPatterns)) {
1557
+ continue;
1558
+ }
1559
+ for (const usage of collectIdentifiersNamed(node.body, propName)) {
1560
+ if (isForwardedPropReference(usage)) {
1561
+ context.report({
1562
+ data: { component: componentName, prop: propName },
1563
+ messageId: "forwardedProp",
1564
+ node: usage.parent.parent
1565
+ });
1566
+ }
1567
+ }
1568
+ }
1569
+ }
1570
+ function reportIfTunnelComponent(node) {
1571
+ const componentName = getFunctionName(node);
1572
+ if (!isPascalCaseName(componentName)) {
1573
+ return;
1574
+ }
1575
+ const { propNames, restName } = getObjectPatternPropNames(node.params[0]);
1576
+ if (restName) {
1577
+ reportSpreadTunnel(node, componentName, restName);
1578
+ }
1579
+ reportForwardedProps(node, componentName, propNames);
1580
+ }
1581
+ return {
1582
+ ArrowFunctionExpression: reportIfTunnelComponent,
1583
+ FunctionDeclaration: reportIfTunnelComponent,
1584
+ FunctionExpression: reportIfTunnelComponent
1585
+ };
1586
+ }
1587
+ };
1588
+
1390
1589
  // src/utils/get-no-functions-inside-components-options.ts
1391
1590
  function getNoFunctionsInsideComponentsOptions(options = {}) {
1392
1591
  return {
@@ -1647,6 +1846,8 @@ var rules = {
1647
1846
  "max-hook-size": maxHookSize,
1648
1847
  "no-deep-relative-imports": noDeepRelativeImports,
1649
1848
  "no-default-export": noDefaultExport,
1849
+ "no-emoji": noEmoji,
1850
+ "no-tunnel-props": noTunnelProps,
1650
1851
  "no-functions-inside-components": noFunctionsInsideComponents,
1651
1852
  "no-try-catch": noTryCatch,
1652
1853
  "prefer-ts-pattern": preferTsPattern,
@@ -1657,4 +1858,4 @@ var rules = {
1657
1858
  export {
1658
1859
  rules
1659
1860
  };
1660
- //# sourceMappingURL=chunk-G6TJUBFT.mjs.map
1861
+ //# sourceMappingURL=chunk-O4RTJGEU.mjs.map