@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 +82 -0
- 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-OVPAO7Q3.mjs → chunk-5P6OHVFD.mjs} +2 -2
- package/dist/{chunk-LDAR6XT6.mjs → chunk-EGNXI5HL.mjs} +6 -1
- package/dist/chunk-EGNXI5HL.mjs.map +1 -0
- package/dist/{chunk-G6TJUBFT.mjs → chunk-O4RTJGEU.mjs} +202 -1
- package/dist/chunk-O4RTJGEU.mjs.map +1 -0
- package/dist/{chunk-72XIHMY7.mjs → chunk-QRYL7RJO.mjs} +3 -2
- package/dist/{chunk-72XIHMY7.mjs.map → chunk-QRYL7RJO.mjs.map} +1 -1
- package/dist/index.js +208 -1
- 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 +206 -0
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-G6TJUBFT.mjs.map +0 -1
- package/dist/chunk-LDAR6XT6.mjs.map +0 -1
- /package/dist/{chunk-OVPAO7Q3.mjs.map → chunk-5P6OHVFD.mjs.map} +0 -0
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
|
package/dist/astro/index.d.mts
CHANGED
|
@@ -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;
|
package/dist/astro/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/astro/index.js
CHANGED
|
@@ -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",
|
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-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;;;
|
|
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"]}
|
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-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-
|
|
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-
|
|
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-
|
|
1861
|
+
//# sourceMappingURL=chunk-O4RTJGEU.mjs.map
|