@skapxd/eslint-opinionated 0.1.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.
Files changed (42) hide show
  1. package/README.md +731 -0
  2. package/dist/astro/index.d.mts +33 -0
  3. package/dist/astro/index.d.ts +33 -0
  4. package/dist/astro/index.js +72 -0
  5. package/dist/astro/index.js.map +1 -0
  6. package/dist/astro/index.mjs +8 -0
  7. package/dist/astro/index.mjs.map +1 -0
  8. package/dist/chunk-3FB4H7N6.mjs +31 -0
  9. package/dist/chunk-3FB4H7N6.mjs.map +1 -0
  10. package/dist/chunk-BAHAXSWA.mjs +62 -0
  11. package/dist/chunk-BAHAXSWA.mjs.map +1 -0
  12. package/dist/chunk-CQKEQ32W.mjs +99 -0
  13. package/dist/chunk-CQKEQ32W.mjs.map +1 -0
  14. package/dist/chunk-RP7BOODV.mjs +1550 -0
  15. package/dist/chunk-RP7BOODV.mjs.map +1 -0
  16. package/dist/cli.d.mts +1 -0
  17. package/dist/cli.d.ts +1 -0
  18. package/dist/cli.js +3451 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/cli.mjs +3428 -0
  21. package/dist/cli.mjs.map +1 -0
  22. package/dist/index.d.mts +14 -0
  23. package/dist/index.d.ts +14 -0
  24. package/dist/index.js +1781 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/index.mjs +44 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/next/index.d.mts +65 -0
  29. package/dist/next/index.d.ts +65 -0
  30. package/dist/next/index.js +103 -0
  31. package/dist/next/index.js.map +1 -0
  32. package/dist/next/index.mjs +8 -0
  33. package/dist/next/index.mjs.map +1 -0
  34. package/dist/rules-qISQhAKV.d.mts +5 -0
  35. package/dist/rules-qISQhAKV.d.ts +5 -0
  36. package/dist/shared/index.d.mts +110 -0
  37. package/dist/shared/index.d.ts +110 -0
  38. package/dist/shared/index.js +1684 -0
  39. package/dist/shared/index.js.map +1 -0
  40. package/dist/shared/index.mjs +15 -0
  41. package/dist/shared/index.mjs.map +1 -0
  42. package/package.json +80 -0
@@ -0,0 +1,1550 @@
1
+ // src/utils/get-file-name.ts
2
+ function getFileName(filename) {
3
+ return filename.split(/[\\/]/).at(-1) ?? filename;
4
+ }
5
+
6
+ // src/utils/get-source-extension.ts
7
+ function getSourceExtension(fileName) {
8
+ return fileName.endsWith(".tsx") ? ".tsx" : ".ts";
9
+ }
10
+
11
+ // src/utils/get-directory-name.ts
12
+ function getDirectoryName(filename) {
13
+ return filename.split(/[\\/]/).slice(0, -1).join("/");
14
+ }
15
+
16
+ // src/utils/is-http-route-method.ts
17
+ function isHttpRouteMethod(functionName) {
18
+ return ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"].includes(
19
+ functionName
20
+ );
21
+ }
22
+
23
+ // src/utils/to-kebab-case.ts
24
+ function toKebabCase(value) {
25
+ return value.replace(/OEmbed/g, "Oembed").replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLocaleLowerCase();
26
+ }
27
+
28
+ // src/utils/get-suggested-helper-file-name.ts
29
+ function getSuggestedHelperFileName({ extension, fileStem, functionName }) {
30
+ const helperFunctionName = fileStem === "route" && isHttpRouteMethod(functionName) ? `handle${functionName}` : functionName;
31
+ return `${toKebabCase(helperFunctionName)}${extension}`;
32
+ }
33
+
34
+ // src/utils/get-path-parts.ts
35
+ function getPathParts(filename) {
36
+ return filename.split(/[\\/]/).filter(Boolean);
37
+ }
38
+
39
+ // src/utils/is-in-source-root.ts
40
+ function isInSourceRoot(filename) {
41
+ const pathParts = getPathParts(filename);
42
+ return pathParts.at(-2) === "src";
43
+ }
44
+
45
+ // src/utils/is-inside-app-directory.ts
46
+ function isInsideAppDirectory(filename) {
47
+ return getPathParts(filename).includes("app");
48
+ }
49
+
50
+ // src/constants/next-app-metadata-file-stems.ts
51
+ var nextAppMetadataFileStems = [
52
+ "apple-icon",
53
+ "icon",
54
+ "manifest",
55
+ "opengraph-image",
56
+ "robots",
57
+ "sitemap",
58
+ "twitter-image"
59
+ ];
60
+
61
+ // src/constants/next-app-route-segment-file-stems.ts
62
+ var nextAppRouteSegmentFileStems = [
63
+ "default",
64
+ "error",
65
+ "forbidden",
66
+ "global-error",
67
+ "global-not-found",
68
+ "layout",
69
+ "loading",
70
+ "not-found",
71
+ "page",
72
+ "route",
73
+ "template",
74
+ "unauthorized"
75
+ ];
76
+
77
+ // src/constants/next-project-root-file-stems.ts
78
+ var nextProjectRootFileStems = [
79
+ "instrumentation",
80
+ "instrumentation-client",
81
+ "mdx-components",
82
+ "proxy"
83
+ ];
84
+
85
+ // src/utils/is-next-convention-file.ts
86
+ function isNextConventionFile({ fileStem, filename }) {
87
+ if ([
88
+ ...nextAppRouteSegmentFileStems,
89
+ ...nextAppMetadataFileStems
90
+ ].includes(fileStem)) {
91
+ return isInsideAppDirectory(filename);
92
+ }
93
+ if (nextProjectRootFileStems.includes(fileStem)) {
94
+ return isInSourceRoot(filename);
95
+ }
96
+ return false;
97
+ }
98
+
99
+ // src/utils/get-suggested-helper-path.ts
100
+ function getSuggestedHelperPath({ extension, fileStem, filename, functionName }) {
101
+ const helperFileName = getSuggestedHelperFileName({
102
+ extension,
103
+ fileStem,
104
+ functionName
105
+ });
106
+ if (isNextConventionFile({ fileStem, filename })) {
107
+ return `${getDirectoryName(filename)}/${helperFileName}`;
108
+ }
109
+ return `${getDirectoryName(filename)}/${toKebabCase(fileStem)}/${helperFileName}`;
110
+ }
111
+
112
+ // src/utils/get-move-suggestion.ts
113
+ function getMoveSuggestion({ filename, functionName }) {
114
+ const fileName = getFileName(filename);
115
+ const extension = getSourceExtension(fileName);
116
+ const fileStem = fileName.slice(0, -extension.length);
117
+ const suggestedPath = getSuggestedHelperPath({
118
+ extension,
119
+ fileStem,
120
+ filename,
121
+ functionName
122
+ });
123
+ if (fileStem === "route" && isHttpRouteMethod(functionName)) {
124
+ return `Mueve la implementacion de \`${functionName}\` a \`${suggestedPath}\` si solo se usa aqui y deja \`${functionName}\` en route.ts delegando a ese helper. No conviertas route.ts en route/index.ts porque Next no lo reconoce.`;
125
+ }
126
+ if (isNextConventionFile({ fileStem, filename })) {
127
+ return `Mueve \`${functionName}\` a \`${suggestedPath}\` si solo se usa aqui y deja \`${fileName}\` como entrypoint de Next. No conviertas \`${fileName}\` en \`${fileStem}/index${extension}\` porque Next exige el nombre exacto del archivo.`;
128
+ }
129
+ return `Mueve \`${functionName}\` a \`${suggestedPath}\` si solo se usa aqui.`;
130
+ }
131
+
132
+ // src/utils/get-function-node-name.ts
133
+ function getFunctionNodeName(node) {
134
+ return node.id?.name ?? "helper";
135
+ }
136
+
137
+ // src/utils/get-variable-declarator-name.ts
138
+ function getVariableDeclaratorName(variableDeclarator) {
139
+ return variableDeclarator.id.type === "Identifier" ? variableDeclarator.id.name : getFunctionNodeName(variableDeclarator.init);
140
+ }
141
+
142
+ // src/utils/is-function-node.ts
143
+ function isFunctionNode(node) {
144
+ return node?.type === "FunctionDeclaration" || node?.type === "FunctionExpression" || node?.type === "ArrowFunctionExpression";
145
+ }
146
+
147
+ // src/utils/get-root-function-entries.ts
148
+ function getRootFunctionEntries(statement) {
149
+ const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
150
+ if (!declaration) {
151
+ return [];
152
+ }
153
+ if (isFunctionNode(declaration)) {
154
+ return [
155
+ {
156
+ name: getFunctionNodeName(declaration),
157
+ node: declaration
158
+ }
159
+ ];
160
+ }
161
+ if (declaration.type !== "VariableDeclaration") {
162
+ return [];
163
+ }
164
+ return declaration.declarations.filter((variableDeclarator) => isFunctionNode(variableDeclarator.init)).map((variableDeclarator) => ({
165
+ name: getVariableDeclaratorName(variableDeclarator),
166
+ node: variableDeclarator.init
167
+ }));
168
+ }
169
+
170
+ // src/utils/get-suggested-helper-file-names.ts
171
+ function getSuggestedHelperFileNames({ extension, fileStem, functionNames }) {
172
+ return [
173
+ ...new Set(
174
+ functionNames.map(
175
+ (functionName) => getSuggestedHelperFileName({
176
+ extension,
177
+ fileStem,
178
+ functionName
179
+ })
180
+ )
181
+ )
182
+ ];
183
+ }
184
+
185
+ // src/utils/get-tree-child-lines.ts
186
+ function getTreeChildLines({ indent = "", names }) {
187
+ return names.map((name, index) => {
188
+ const branch = index === names.length - 1 ? "\u2514\u2500\u2500" : "\u251C\u2500\u2500";
189
+ return `${indent}${branch} ${name}`;
190
+ });
191
+ }
192
+
193
+ // src/utils/get-structure-suggestion.ts
194
+ function getStructureSuggestion({ filename, functionNames }) {
195
+ const fileName = getFileName(filename);
196
+ const extension = getSourceExtension(fileName);
197
+ const fileStem = fileName.slice(0, -extension.length);
198
+ const directoryName = getDirectoryName(filename);
199
+ const helperFileNames = getSuggestedHelperFileNames({
200
+ extension,
201
+ fileStem,
202
+ functionNames
203
+ });
204
+ if (isNextConventionFile({ fileStem, filename })) {
205
+ return [
206
+ `${directoryName}/`,
207
+ ...getTreeChildLines({
208
+ names: [fileName, ...helperFileNames]
209
+ })
210
+ ].join("\n");
211
+ }
212
+ return [
213
+ `${directoryName}/${toKebabCase(fileStem)}/`,
214
+ ...getTreeChildLines({
215
+ names: [`index${extension}`, ...helperFileNames]
216
+ })
217
+ ].join("\n");
218
+ }
219
+
220
+ // src/rules/one-root-function-per-file.ts
221
+ var oneRootFunctionPerFile = {
222
+ meta: {
223
+ type: "suggestion",
224
+ docs: {
225
+ description: "Limita cada archivo a una sola funcion declarada en la raiz."
226
+ },
227
+ messages: {
228
+ tooManyRootFunctions: "Este archivo tiene {{count}} funciones en la raiz. Deja solo una funcion top-level por archivo. {{moveSuggestion}}\n\nEstructura sugerida:\n{{structureSuggestion}}\n\nSi se reutiliza, muevela a un modulo compartido con nombre de dominio."
229
+ },
230
+ schema: []
231
+ },
232
+ create(context) {
233
+ return {
234
+ Program(node) {
235
+ const rootFunctions = node.body.flatMap(
236
+ (statement) => getRootFunctionEntries(statement)
237
+ );
238
+ if (rootFunctions.length <= 1) {
239
+ return;
240
+ }
241
+ const firstHelper = rootFunctions[1];
242
+ const helperFunctionNames = rootFunctions.slice(1).map((rootFunction) => rootFunction.name);
243
+ context.report({
244
+ data: {
245
+ count: String(rootFunctions.length),
246
+ moveSuggestion: getMoveSuggestion({
247
+ filename: context.filename ?? context.getFilename(),
248
+ functionName: firstHelper.name
249
+ }),
250
+ structureSuggestion: getStructureSuggestion({
251
+ filename: context.filename ?? context.getFilename(),
252
+ functionNames: helperFunctionNames
253
+ })
254
+ },
255
+ messageId: "tooManyRootFunctions",
256
+ node: firstHelper.node
257
+ });
258
+ }
259
+ };
260
+ }
261
+ };
262
+
263
+ // src/utils/is-ast-node.ts
264
+ function isAstNode(value) {
265
+ return Boolean(value && typeof value === "object" && "type" in value);
266
+ }
267
+
268
+ // src/utils/get-node-children.ts
269
+ function getNodeChildren(node) {
270
+ return Object.entries(node).flatMap(([key, value]) => {
271
+ if (["parent", "loc", "range", "tokens", "comments"].includes(key)) {
272
+ return [];
273
+ }
274
+ if (Array.isArray(value)) {
275
+ return value.filter(isAstNode);
276
+ }
277
+ return isAstNode(value) ? [value] : [];
278
+ });
279
+ }
280
+
281
+ // src/utils/contains-jsx.ts
282
+ function containsJsx(node) {
283
+ if (!isAstNode(node)) {
284
+ return false;
285
+ }
286
+ if (node.type === "JSXElement" || node.type === "JSXFragment") {
287
+ return true;
288
+ }
289
+ return getNodeChildren(node).some((child) => containsJsx(child));
290
+ }
291
+
292
+ // src/utils/contains-own-jsx.ts
293
+ function containsOwnJsx(node) {
294
+ if (!isAstNode(node)) {
295
+ return false;
296
+ }
297
+ if (node.type === "JSXElement" || node.type === "JSXFragment") {
298
+ return true;
299
+ }
300
+ if (isFunctionNode(node)) {
301
+ return false;
302
+ }
303
+ return getNodeChildren(node).some((child) => containsOwnJsx(child));
304
+ }
305
+
306
+ // src/utils/function-returns-jsx.ts
307
+ function functionReturnsJsx(functionNode) {
308
+ if (functionNode.body?.type !== "BlockStatement") {
309
+ return containsJsx(functionNode.body);
310
+ }
311
+ return containsOwnJsx(functionNode.body);
312
+ }
313
+
314
+ // src/utils/is-pascal-case-name.ts
315
+ function isPascalCaseName(value) {
316
+ return /^[A-Z][A-Za-z0-9]*$/.test(value);
317
+ }
318
+
319
+ // src/utils/to-pascal-case.ts
320
+ function toPascalCase(value) {
321
+ return value.replace(/^[a-z]/, (letter) => letter.toLocaleUpperCase());
322
+ }
323
+
324
+ // src/rules/jsx-return-name-pascal-case.ts
325
+ var jsxReturnNamePascalCase = {
326
+ meta: {
327
+ type: "problem",
328
+ docs: {
329
+ description: "Exige nombres PascalCase para funciones que devuelven JSX."
330
+ },
331
+ messages: {
332
+ invalidName: "La funcion `{{name}}` devuelve JSX. Nombrala como componente, por ejemplo `{{suggestedName}}`, y usala con sintaxis JSX si aplica."
333
+ },
334
+ schema: []
335
+ },
336
+ create(context) {
337
+ function reportIfJsxReturningFunction(node, name, reportNode = node) {
338
+ if (!name || isPascalCaseName(name) || !functionReturnsJsx(node)) {
339
+ return;
340
+ }
341
+ context.report({
342
+ data: {
343
+ name,
344
+ suggestedName: toPascalCase(name)
345
+ },
346
+ messageId: "invalidName",
347
+ node: reportNode
348
+ });
349
+ }
350
+ return {
351
+ FunctionDeclaration(node) {
352
+ reportIfJsxReturningFunction(node, node.id?.name, node.id ?? node);
353
+ },
354
+ VariableDeclarator(node) {
355
+ if (!isFunctionNode(node.init) || node.id.type !== "Identifier") {
356
+ return;
357
+ }
358
+ reportIfJsxReturningFunction(node.init, node.id.name, node.id);
359
+ }
360
+ };
361
+ }
362
+ };
363
+
364
+ // src/utils/is-member-property-named.ts
365
+ function isMemberPropertyNamed(node, propertyName) {
366
+ if (node.computed) {
367
+ return node.property.type === "Literal" && node.property.value === propertyName;
368
+ }
369
+ return node.property.type === "Identifier" && node.property.name === propertyName;
370
+ }
371
+
372
+ // src/utils/is-callee-named.ts
373
+ function isCalleeNamed(node, names) {
374
+ if (node?.type === "Identifier") {
375
+ return names.includes(node.name);
376
+ }
377
+ if (node?.type === "MemberExpression") {
378
+ return names.some((name) => isMemberPropertyNamed(node, name));
379
+ }
380
+ return false;
381
+ }
382
+
383
+ // src/utils/contains-call-named.ts
384
+ function containsCallNamed(node, names) {
385
+ if (!isAstNode(node)) {
386
+ return false;
387
+ }
388
+ if (node.type === "CallExpression" && isCalleeNamed(node.callee, names)) {
389
+ return true;
390
+ }
391
+ return getNodeChildren(node).some((child) => containsCallNamed(child, names));
392
+ }
393
+
394
+ // src/utils/get-async-result-rule-options.ts
395
+ function getAsyncResultRuleOptions(options = {}) {
396
+ return {
397
+ allowFilePatterns: options.allowFilePatterns ?? [],
398
+ allowNamePatterns: options.allowNamePatterns ?? [],
399
+ checkMissingReturnType: options.checkMissingReturnType ?? true,
400
+ checkMissingReturnTypeWhenCallNames: options.checkMissingReturnTypeWhenCallNames ?? [],
401
+ promiseTypeNames: options.promiseTypeNames ?? ["Promise"],
402
+ requireCallNames: options.requireCallNames ?? [],
403
+ resultTypeNames: options.resultTypeNames ?? ["Result"]
404
+ };
405
+ }
406
+
407
+ // src/utils/get-property-name.ts
408
+ function getPropertyName(node) {
409
+ if (!node) {
410
+ return "anonymous";
411
+ }
412
+ if (node.type === "Identifier") {
413
+ return node.name;
414
+ }
415
+ if (node.type === "Literal") {
416
+ return String(node.value);
417
+ }
418
+ return "anonymous";
419
+ }
420
+
421
+ // src/utils/get-parent-function-name.ts
422
+ function getParentFunctionName(node) {
423
+ const parent = node.parent;
424
+ if (parent?.type === "VariableDeclarator") {
425
+ return getVariableDeclaratorName(parent);
426
+ }
427
+ if (parent?.type === "Property" || parent?.type === "MethodDefinition" || parent?.type === "PropertyDefinition") {
428
+ return getPropertyName(parent.key);
429
+ }
430
+ return getFunctionNodeName(node);
431
+ }
432
+
433
+ // src/utils/get-function-expression-name.ts
434
+ function getFunctionExpressionName(node) {
435
+ return node.id?.name ?? getParentFunctionName(node);
436
+ }
437
+
438
+ // src/utils/get-parent-function-report-node.ts
439
+ function getParentFunctionReportNode(node) {
440
+ const parent = node.parent;
441
+ if (parent?.type === "VariableDeclarator" && parent.id.type === "Identifier") {
442
+ return parent.id;
443
+ }
444
+ if (parent?.type === "Property" || parent?.type === "MethodDefinition" || parent?.type === "PropertyDefinition") {
445
+ return parent.key;
446
+ }
447
+ return node.id ?? node;
448
+ }
449
+
450
+ // src/utils/get-type-context.ts
451
+ function getTypeContext(context) {
452
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
453
+ const parserServices = sourceCode.parserServices;
454
+ if (!parserServices?.program) {
455
+ return null;
456
+ }
457
+ return {
458
+ checker: parserServices.program.getTypeChecker(),
459
+ services: parserServices
460
+ };
461
+ }
462
+
463
+ // src/utils/is-anonymous-generated-function-name.ts
464
+ function isAnonymousGeneratedFunctionName(name) {
465
+ return name === "anonymous" || name === "helper";
466
+ }
467
+
468
+ // src/utils/get-type-reference-parameters.ts
469
+ function getTypeReferenceParameters(node) {
470
+ return node.typeArguments?.params ?? node.typeParameters?.params ?? [];
471
+ }
472
+
473
+ // src/utils/is-type-reference-named.ts
474
+ function isTypeReferenceNamed(node, names) {
475
+ const typeName = node.typeName;
476
+ return typeName.type === "Identifier" && names.includes(typeName.name);
477
+ }
478
+
479
+ // src/utils/is-promise-of-result-type.ts
480
+ function isPromiseOfResultType(node, options) {
481
+ if (node.type !== "TSTypeReference") {
482
+ return false;
483
+ }
484
+ if (!isTypeReferenceNamed(node, options.promiseTypeNames)) {
485
+ return false;
486
+ }
487
+ const promiseValueType = getTypeReferenceParameters(node)[0];
488
+ return Boolean(
489
+ promiseValueType && promiseValueType.type === "TSTypeReference" && isTypeReferenceNamed(promiseValueType, options.resultTypeNames)
490
+ );
491
+ }
492
+
493
+ // src/utils/get-package-name.ts
494
+ import { trySafe } from "@skapxd/result";
495
+ import fs from "fs";
496
+ import path from "path";
497
+ var packageNameByDir = /* @__PURE__ */ new Map();
498
+ function getPackageName(fileName) {
499
+ const visited = [];
500
+ let dir = path.dirname(fileName);
501
+ while (true) {
502
+ if (packageNameByDir.has(dir)) {
503
+ const cached = packageNameByDir.get(dir);
504
+ for (const visitedDir of visited) packageNameByDir.set(visitedDir, cached);
505
+ return cached;
506
+ }
507
+ visited.push(dir);
508
+ const packageJsonPath = path.join(dir, "package.json");
509
+ if (fs.existsSync(packageJsonPath)) {
510
+ const parsed = trySafe(
511
+ () => JSON.parse(fs.readFileSync(packageJsonPath, "utf8"))
512
+ );
513
+ const name = parsed.ok ? parsed.value.name ?? null : null;
514
+ for (const visitedDir of visited) packageNameByDir.set(visitedDir, name);
515
+ return name;
516
+ }
517
+ const parent = path.dirname(dir);
518
+ if (parent === dir) {
519
+ for (const visitedDir of visited) packageNameByDir.set(visitedDir, null);
520
+ return null;
521
+ }
522
+ dir = parent;
523
+ }
524
+ }
525
+
526
+ // src/utils/is-skapxd-result-source-file.ts
527
+ function isSkapxdResultSourceFile(fileName) {
528
+ return getPackageName(fileName) === "@skapxd/result";
529
+ }
530
+
531
+ // src/utils/resolve-alias-symbol.ts
532
+ import ts from "typescript";
533
+ function resolveAliasSymbol(symbol, typeContext) {
534
+ return symbol.flags & ts.SymbolFlags.Alias ? typeContext.checker.getAliasedSymbol(symbol) : symbol;
535
+ }
536
+
537
+ // src/utils/is-symbol-from-skapxd-result.ts
538
+ function isSymbolFromSkapxdResult(symbol, typeContext) {
539
+ const resolvedSymbol = resolveAliasSymbol(symbol, typeContext);
540
+ return (resolvedSymbol.getDeclarations() ?? []).some(
541
+ (declaration) => isSkapxdResultSourceFile(declaration.getSourceFile().fileName)
542
+ );
543
+ }
544
+
545
+ // src/utils/is-skapxd-named-type.ts
546
+ function isSkapxdNamedType(type, names, typeContext) {
547
+ return [
548
+ type.aliasSymbol,
549
+ type.symbol
550
+ ].some(
551
+ (symbol) => Boolean(
552
+ symbol && names.includes(symbol.getName()) && isSymbolFromSkapxdResult(symbol, typeContext)
553
+ )
554
+ );
555
+ }
556
+
557
+ // src/utils/is-skapxd-result-type.ts
558
+ function isSkapxdResultType(type, typeContext) {
559
+ if (isSkapxdNamedType(type, ["Result", "SafeResult"], typeContext)) {
560
+ return true;
561
+ }
562
+ if (!type.isUnion()) {
563
+ return false;
564
+ }
565
+ const hasOk = type.types.some(
566
+ (part) => isSkapxdNamedType(part, ["Ok"], typeContext)
567
+ );
568
+ const hasErr = type.types.some(
569
+ (part) => isSkapxdNamedType(part, ["Err"], typeContext)
570
+ );
571
+ return hasOk && hasErr;
572
+ }
573
+
574
+ // src/utils/is-skapxd-result-or-promise-result-type.ts
575
+ function isSkapxdResultOrPromiseResultType(type, typeContext) {
576
+ if (isSkapxdResultType(type, typeContext)) {
577
+ return true;
578
+ }
579
+ const promisedType = typeContext.checker.getPromisedTypeOfPromise(type);
580
+ return Boolean(promisedType && isSkapxdResultType(promisedType, typeContext));
581
+ }
582
+
583
+ // src/utils/matches-any-pattern.ts
584
+ function matchesAnyPattern(value, patterns) {
585
+ return patterns.some((pattern) => new RegExp(pattern).test(value));
586
+ }
587
+
588
+ // src/rules/async-functions-return-result.ts
589
+ var asyncFunctionsReturnResult = {
590
+ meta: {
591
+ type: "problem",
592
+ docs: {
593
+ description: "Exige Promise<Result<...>> en funciones async de dominio."
594
+ },
595
+ messages: {
596
+ missingReturnType: "La funcion async `{{name}}` debe declarar Promise<Result<...>> como tipo de retorno.",
597
+ invalidReturnType: "La funcion async `{{name}}` debe retornar Promise<Result<...>> para modelar errores de forma explicita."
598
+ },
599
+ schema: [
600
+ {
601
+ additionalProperties: false,
602
+ properties: {
603
+ allowFilePatterns: {
604
+ items: { type: "string" },
605
+ type: "array"
606
+ },
607
+ allowNamePatterns: {
608
+ items: { type: "string" },
609
+ type: "array"
610
+ },
611
+ checkMissingReturnType: { type: "boolean" },
612
+ checkMissingReturnTypeWhenCallNames: {
613
+ items: { type: "string" },
614
+ type: "array"
615
+ },
616
+ requireCallNames: {
617
+ items: { type: "string" },
618
+ type: "array"
619
+ },
620
+ promiseTypeNames: {
621
+ items: { type: "string" },
622
+ type: "array"
623
+ },
624
+ resultTypeNames: {
625
+ items: { type: "string" },
626
+ type: "array"
627
+ }
628
+ },
629
+ type: "object"
630
+ }
631
+ ]
632
+ },
633
+ create(context) {
634
+ const options = getAsyncResultRuleOptions(context.options[0]);
635
+ const filename = context.filename ?? context.getFilename();
636
+ const typeContext = getTypeContext(context);
637
+ function isSkapxdResultReturnType(annotation) {
638
+ if (typeContext) {
639
+ const type = typeContext.services.getTypeFromTypeNode(annotation);
640
+ return isSkapxdResultOrPromiseResultType(type, typeContext);
641
+ }
642
+ return isPromiseOfResultType(annotation, options);
643
+ }
644
+ function reportIfInvalidAsyncReturn(node, name, reportNode = node) {
645
+ if (!node.async || matchesAnyPattern(filename, options.allowFilePatterns)) {
646
+ return;
647
+ }
648
+ const functionName = name ?? "anonymous";
649
+ if (isAnonymousGeneratedFunctionName(functionName)) {
650
+ return;
651
+ }
652
+ if (matchesAnyPattern(functionName, options.allowNamePatterns)) {
653
+ return;
654
+ }
655
+ if (options.requireCallNames.length && !containsCallNamed(node.body, options.requireCallNames)) {
656
+ return;
657
+ }
658
+ const returnType = node.returnType?.typeAnnotation;
659
+ if (!returnType) {
660
+ if (options.checkMissingReturnType || containsCallNamed(node.body, options.checkMissingReturnTypeWhenCallNames)) {
661
+ context.report({
662
+ data: { name: functionName },
663
+ messageId: "missingReturnType",
664
+ node: reportNode
665
+ });
666
+ }
667
+ return;
668
+ }
669
+ if (isSkapxdResultReturnType(returnType)) {
670
+ return;
671
+ }
672
+ context.report({
673
+ data: { name: functionName },
674
+ messageId: "invalidReturnType",
675
+ node: reportNode
676
+ });
677
+ }
678
+ return {
679
+ ArrowFunctionExpression(node) {
680
+ reportIfInvalidAsyncReturn(
681
+ node,
682
+ getParentFunctionName(node),
683
+ getParentFunctionReportNode(node)
684
+ );
685
+ },
686
+ FunctionDeclaration(node) {
687
+ reportIfInvalidAsyncReturn(node, node.id?.name, node.id ?? node);
688
+ },
689
+ FunctionExpression(node) {
690
+ reportIfInvalidAsyncReturn(
691
+ node,
692
+ getFunctionExpressionName(node),
693
+ getParentFunctionReportNode(node)
694
+ );
695
+ }
696
+ };
697
+ }
698
+ };
699
+
700
+ // src/utils/get-containing-function.ts
701
+ function getContainingFunction(node) {
702
+ let currentNode = node.parent;
703
+ while (currentNode) {
704
+ if (isFunctionNode(currentNode)) {
705
+ return currentNode;
706
+ }
707
+ currentNode = currentNode.parent;
708
+ }
709
+ return null;
710
+ }
711
+
712
+ // src/utils/get-function-name.ts
713
+ function getFunctionName(node) {
714
+ if (node.type === "FunctionDeclaration") {
715
+ return getFunctionNodeName(node);
716
+ }
717
+ return getParentFunctionName(node);
718
+ }
719
+
720
+ // src/utils/get-returned-object-expression.ts
721
+ function getReturnedObjectExpression(node) {
722
+ if (!node) {
723
+ return null;
724
+ }
725
+ if (node.type === "ObjectExpression") {
726
+ return node;
727
+ }
728
+ if (node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression" || node.type === "ChainExpression") {
729
+ return getReturnedObjectExpression(node.expression);
730
+ }
731
+ return null;
732
+ }
733
+
734
+ // src/utils/unwrap-expression.ts
735
+ function unwrapExpression(node) {
736
+ if (node.type === "ChainExpression" || node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSNonNullExpression") {
737
+ return unwrapExpression(node.expression);
738
+ }
739
+ return node;
740
+ }
741
+
742
+ // src/utils/get-boolean-literal-value.ts
743
+ function getBooleanLiteralValue(node) {
744
+ const unwrappedNode = unwrapExpression(node);
745
+ return unwrappedNode.type === "Literal" && typeof unwrappedNode.value === "boolean" ? unwrappedNode.value : null;
746
+ }
747
+
748
+ // src/utils/is-property-key-named.ts
749
+ function isPropertyKeyNamed(property, propertyName) {
750
+ if (property.key.type === "Identifier") {
751
+ return property.key.name === propertyName;
752
+ }
753
+ return property.key.type === "Literal" && property.key.value === propertyName;
754
+ }
755
+
756
+ // src/utils/has-boolean-ok-property.ts
757
+ function hasBooleanOkProperty(node) {
758
+ return node.properties.some(
759
+ (property) => property.type === "Property" && isPropertyKeyNamed(property, "ok") && getBooleanLiteralValue(property.value) !== null
760
+ );
761
+ }
762
+
763
+ // src/utils/is-exported-function.ts
764
+ function isExportedFunction(node) {
765
+ const parent = node.parent;
766
+ if (node.type === "FunctionDeclaration" && (parent?.type === "ExportNamedDeclaration" || parent?.type === "ExportDefaultDeclaration")) {
767
+ return true;
768
+ }
769
+ if ((node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") && parent?.type === "VariableDeclarator" && parent.parent?.parent?.type === "ExportNamedDeclaration") {
770
+ return true;
771
+ }
772
+ return false;
773
+ }
774
+
775
+ // src/rules/no-ad-hoc-ok-result.ts
776
+ var noAdHocOkResult = {
777
+ meta: {
778
+ type: "problem",
779
+ docs: {
780
+ description: "Prohibe retornar contratos ad hoc con ok en funciones async exportadas."
781
+ },
782
+ messages: {
783
+ adHocOkResult: "No retornes objetos ad hoc con `ok` desde la funcion async `{{name}}`. Usa Result.ok(...) / Result.err(...) con un error discriminado `{ type: ... }`."
784
+ },
785
+ schema: []
786
+ },
787
+ create(context) {
788
+ return {
789
+ ReturnStatement(node) {
790
+ const returnedObject = getReturnedObjectExpression(node.argument);
791
+ if (!returnedObject || !hasBooleanOkProperty(returnedObject)) {
792
+ return;
793
+ }
794
+ const containingFunction = getContainingFunction(node);
795
+ if (!containingFunction?.async || !isExportedFunction(containingFunction)) {
796
+ return;
797
+ }
798
+ context.report({
799
+ data: {
800
+ name: getFunctionName(containingFunction)
801
+ },
802
+ messageId: "adHocOkResult",
803
+ node
804
+ });
805
+ }
806
+ };
807
+ }
808
+ };
809
+
810
+ // src/utils/get-await-requires-try-safe-options.ts
811
+ function getAwaitRequiresTrySafeOptions(options = {}) {
812
+ return {
813
+ allowFilePatterns: options.allowFilePatterns ?? [],
814
+ trySafeCallNames: options.trySafeCallNames ?? ["trySafe"]
815
+ };
816
+ }
817
+
818
+ // src/utils/get-await-scope-name.ts
819
+ function getAwaitScopeName(node) {
820
+ const containingFunction = getContainingFunction(node);
821
+ return containingFunction ? getFunctionName(containingFunction) : "top-level";
822
+ }
823
+
824
+ // src/utils/get-enclosing-try-safe-call.ts
825
+ function getEnclosingTrySafeCall(node, trySafeCallNames) {
826
+ let currentNode = node.parent;
827
+ while (currentNode) {
828
+ if (isFunctionNode(currentNode)) {
829
+ const parent = currentNode.parent;
830
+ if (parent?.type === "CallExpression" && parent.arguments.includes(currentNode) && isCalleeNamed(parent.callee, trySafeCallNames)) {
831
+ return parent;
832
+ }
833
+ return null;
834
+ }
835
+ currentNode = currentNode.parent;
836
+ }
837
+ return null;
838
+ }
839
+
840
+ // src/utils/contains-await-expression.ts
841
+ function containsAwaitExpression(node) {
842
+ if (!isAstNode(node)) {
843
+ return false;
844
+ }
845
+ if (node.type === "AwaitExpression") {
846
+ return true;
847
+ }
848
+ if (isFunctionNode(node)) {
849
+ return false;
850
+ }
851
+ return getNodeChildren(node).some((child) => containsAwaitExpression(child));
852
+ }
853
+
854
+ // src/utils/get-call-expression-example.ts
855
+ function getCallExpressionExample(node, sourceCode) {
856
+ const calleeText = sourceCode.getText(node.callee);
857
+ if (node.arguments.length === 0) {
858
+ return `${calleeText}()`;
859
+ }
860
+ if (node.arguments.length === 1 && unwrapExpression(node.arguments[0]).type === "ObjectExpression") {
861
+ return `${calleeText}({...})`;
862
+ }
863
+ return `${calleeText}(...)`;
864
+ }
865
+
866
+ // src/utils/get-awaited-operation-example.ts
867
+ function getAwaitedOperationExample(node, sourceCode) {
868
+ const unwrappedNode = unwrapExpression(node);
869
+ if (unwrappedNode.type === "CallExpression") {
870
+ return getCallExpressionExample(unwrappedNode, sourceCode);
871
+ }
872
+ const expressionText = sourceCode.getText(unwrappedNode).replace(/\s+/g, " ");
873
+ return expressionText.length > 80 ? `${expressionText.slice(0, 77)}...` : expressionText;
874
+ }
875
+
876
+ // src/utils/get-try-safe-await-suggestion.ts
877
+ function getTrySafeAwaitSuggestion(node, sourceCode) {
878
+ const callbackKeyword = containsAwaitExpression(node) ? "async " : "";
879
+ return `await trySafe(${callbackKeyword}() => ${getAwaitedOperationExample(
880
+ node,
881
+ sourceCode
882
+ )})`;
883
+ }
884
+
885
+ // src/utils/is-skapxd-result-or-promise-result-expression.ts
886
+ function isSkapxdResultOrPromiseResultExpression(node, typeContext) {
887
+ return isSkapxdResultOrPromiseResultType(
888
+ typeContext.services.getTypeAtLocation(node),
889
+ typeContext
890
+ );
891
+ }
892
+
893
+ // src/utils/is-try-safe-call.ts
894
+ function isTrySafeCall(node, trySafeCallNames) {
895
+ return node?.type === "CallExpression" && isCalleeNamed(node.callee, trySafeCallNames);
896
+ }
897
+
898
+ // src/rules/await-requires-try-safe.ts
899
+ var awaitRequiresTrySafe = {
900
+ meta: {
901
+ type: "problem",
902
+ docs: {
903
+ description: "Exige que los await esten protegidos por trySafe."
904
+ },
905
+ messages: {
906
+ unprotectedAwait: "El await dentro de `{{name}}` no esta protegido por trySafe. Envuelve la operacion asi: `{{suggestion}}`."
907
+ },
908
+ schema: [
909
+ {
910
+ additionalProperties: false,
911
+ properties: {
912
+ allowFilePatterns: {
913
+ items: { type: "string" },
914
+ type: "array"
915
+ },
916
+ trySafeCallNames: {
917
+ items: { type: "string" },
918
+ type: "array"
919
+ }
920
+ },
921
+ type: "object"
922
+ }
923
+ ]
924
+ },
925
+ create(context) {
926
+ const options = getAwaitRequiresTrySafeOptions(context.options[0]);
927
+ const filename = context.filename ?? context.getFilename();
928
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
929
+ const typeContext = getTypeContext(context);
930
+ function isSkapxdTrySafe(callNode) {
931
+ if (!callNode) {
932
+ return false;
933
+ }
934
+ if (!typeContext) {
935
+ return true;
936
+ }
937
+ const symbol = typeContext.services.getSymbolAtLocation(callNode.callee);
938
+ return Boolean(symbol && isSymbolFromSkapxdResult(symbol, typeContext));
939
+ }
940
+ return {
941
+ AwaitExpression(node) {
942
+ if (matchesAnyPattern(filename, options.allowFilePatterns)) {
943
+ return;
944
+ }
945
+ if (typeContext && isSkapxdResultOrPromiseResultExpression(node.argument, typeContext)) {
946
+ return;
947
+ }
948
+ const directCall = isTrySafeCall(node.argument, options.trySafeCallNames) ? node.argument : null;
949
+ const enclosingCall = getEnclosingTrySafeCall(
950
+ node,
951
+ options.trySafeCallNames
952
+ );
953
+ if (isSkapxdTrySafe(directCall) || isSkapxdTrySafe(enclosingCall)) {
954
+ return;
955
+ }
956
+ context.report({
957
+ data: {
958
+ name: getAwaitScopeName(node),
959
+ suggestion: getTrySafeAwaitSuggestion(node.argument, sourceCode)
960
+ },
961
+ messageId: "unprotectedAwait",
962
+ node
963
+ });
964
+ }
965
+ };
966
+ }
967
+ };
968
+
969
+ // src/utils/get-ok-member-object.ts
970
+ function getOkMemberObject(node) {
971
+ const unwrappedNode = unwrapExpression(node);
972
+ if (unwrappedNode.type !== "MemberExpression" || unwrappedNode.object.type !== "Identifier" || !isMemberPropertyNamed(unwrappedNode, "ok")) {
973
+ return null;
974
+ }
975
+ return {
976
+ name: unwrappedNode.object.name,
977
+ node: unwrappedNode.object
978
+ };
979
+ }
980
+
981
+ // src/utils/is-failed-ok-comparison.ts
982
+ function isFailedOkComparison(operator, comparedValue) {
983
+ return operator === "===" && comparedValue === false || operator === "!==" && comparedValue === true;
984
+ }
985
+
986
+ // src/utils/get-failed-result-binary-guard-name.ts
987
+ function getFailedResultBinaryGuardName(node) {
988
+ const leftResult = getOkMemberObject(node.left);
989
+ const rightResult = getOkMemberObject(node.right);
990
+ const leftBoolean = getBooleanLiteralValue(node.left);
991
+ const rightBoolean = getBooleanLiteralValue(node.right);
992
+ if (leftResult && rightBoolean !== null) {
993
+ return isFailedOkComparison(node.operator, rightBoolean) ? leftResult : null;
994
+ }
995
+ if (rightResult && leftBoolean !== null) {
996
+ return isFailedOkComparison(node.operator, leftBoolean) ? rightResult : null;
997
+ }
998
+ return null;
999
+ }
1000
+
1001
+ // src/utils/get-result-check-argument.ts
1002
+ function getResultCheckArgument(node, methodName) {
1003
+ const unwrappedNode = unwrapExpression(node);
1004
+ if (unwrappedNode.type !== "CallExpression" || unwrappedNode.callee.type !== "MemberExpression" || !isMemberPropertyNamed(unwrappedNode.callee, methodName)) {
1005
+ return null;
1006
+ }
1007
+ const argument = unwrappedNode.arguments[0];
1008
+ if (argument?.type !== "Identifier") {
1009
+ return null;
1010
+ }
1011
+ return { name: argument.name, node: argument };
1012
+ }
1013
+
1014
+ // src/utils/get-failed-result-guard.ts
1015
+ function getFailedResultGuard(node) {
1016
+ const unwrappedNode = unwrapExpression(node);
1017
+ if (unwrappedNode.type === "UnaryExpression" && unwrappedNode.operator === "!") {
1018
+ return getOkMemberObject(unwrappedNode.argument) ?? getResultCheckArgument(unwrappedNode.argument, "isOk");
1019
+ }
1020
+ if (unwrappedNode.type === "CallExpression") {
1021
+ return getResultCheckArgument(unwrappedNode, "isErr");
1022
+ }
1023
+ if (unwrappedNode.type === "BinaryExpression" && ["===", "!=="].includes(unwrappedNode.operator)) {
1024
+ return getFailedResultBinaryGuardName(unwrappedNode);
1025
+ }
1026
+ return null;
1027
+ }
1028
+
1029
+ // src/utils/is-result-err-call.ts
1030
+ function isResultErrCall(node) {
1031
+ return node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier" && isMemberPropertyNamed(node.callee, "err");
1032
+ }
1033
+
1034
+ // src/utils/get-own-result-err-calls.ts
1035
+ function getOwnResultErrCalls(node, isRoot = true) {
1036
+ if (!isAstNode(node)) {
1037
+ return [];
1038
+ }
1039
+ if (isFunctionNode(node) || !isRoot && node.type === "IfStatement") {
1040
+ return [];
1041
+ }
1042
+ const ownCalls = node.type === "CallExpression" && isResultErrCall(node) ? [node] : [];
1043
+ return [
1044
+ ...ownCalls,
1045
+ ...getNodeChildren(node).flatMap((child) => getOwnResultErrCalls(child, false))
1046
+ ];
1047
+ }
1048
+
1049
+ // src/utils/get-function-return-type.ts
1050
+ function getFunctionReturnType(node, typeContext) {
1051
+ if (node.returnType?.typeAnnotation) {
1052
+ return typeContext.services.getTypeFromTypeNode(node.returnType.typeAnnotation);
1053
+ }
1054
+ const functionType = typeContext.services.getTypeAtLocation(node);
1055
+ const signature = functionType.getCallSignatures()[0];
1056
+ return signature?.getReturnType() ?? null;
1057
+ }
1058
+
1059
+ // src/utils/function-returns-skapxd-result-type.ts
1060
+ function functionReturnsSkapxdResultType(node, typeContext) {
1061
+ const returnType = getFunctionReturnType(node, typeContext);
1062
+ return Boolean(returnType && isSkapxdResultOrPromiseResultType(returnType, typeContext));
1063
+ }
1064
+
1065
+ // src/utils/is-inside-skapxd-result-returning-function.ts
1066
+ function isInsideSkapxdResultReturningFunction(node, typeContext) {
1067
+ const containingFunction = getContainingFunction(node);
1068
+ return Boolean(
1069
+ containingFunction && functionReturnsSkapxdResultType(containingFunction, typeContext)
1070
+ );
1071
+ }
1072
+
1073
+ // src/utils/is-skapxd-result-err-call.ts
1074
+ function isSkapxdResultErrCall(node, typeContext) {
1075
+ if (!isResultErrCall(node)) {
1076
+ return false;
1077
+ }
1078
+ const symbol = typeContext.services.getSymbolAtLocation(node.callee.object);
1079
+ return Boolean(symbol && isSymbolFromSkapxdResult(symbol, typeContext));
1080
+ }
1081
+
1082
+ // src/utils/is-skapxd-result-expression.ts
1083
+ function isSkapxdResultExpression(node, typeContext) {
1084
+ return isSkapxdResultType(typeContext.services.getTypeAtLocation(node), typeContext);
1085
+ }
1086
+
1087
+ // src/utils/is-result-error-member.ts
1088
+ function isResultErrorMember(node, resultName) {
1089
+ const unwrappedNode = unwrapExpression(node);
1090
+ return unwrappedNode.type === "MemberExpression" && unwrappedNode.object.type === "Identifier" && unwrappedNode.object.name === resultName && isMemberPropertyNamed(unwrappedNode, "error");
1091
+ }
1092
+
1093
+ // src/utils/result-err-preserves-cause.ts
1094
+ function resultErrPreservesCause(node, resultName) {
1095
+ const unwrappedNode = unwrapExpression(node);
1096
+ if (isResultErrorMember(unwrappedNode, resultName)) {
1097
+ return true;
1098
+ }
1099
+ if (unwrappedNode.type !== "ObjectExpression") {
1100
+ return false;
1101
+ }
1102
+ return unwrappedNode.properties.some((property) => {
1103
+ if (property.type !== "Property" || !isPropertyKeyNamed(property, "cause")) {
1104
+ return false;
1105
+ }
1106
+ return isResultErrorMember(property.value, resultName);
1107
+ });
1108
+ }
1109
+
1110
+ // src/rules/result-error-requires-cause.ts
1111
+ var resultErrorRequiresCause = {
1112
+ meta: {
1113
+ type: "problem",
1114
+ docs: {
1115
+ description: "Exige preservar result.error como cause cuando una funcion que retorna Result transforma un Result fallido en Result.err."
1116
+ },
1117
+ messages: {
1118
+ missingCause: "El error de `{{name}}` ya existe como `{{name}}.error`. Preservalo en Result.err con `cause: {{name}}.error`."
1119
+ },
1120
+ schema: []
1121
+ },
1122
+ create(context) {
1123
+ const typeContext = getTypeContext(context);
1124
+ return {
1125
+ IfStatement(node) {
1126
+ const resultGuard = getFailedResultGuard(node.test);
1127
+ if (!typeContext || !resultGuard || !isSkapxdResultExpression(resultGuard.node, typeContext) || !isInsideSkapxdResultReturningFunction(node, typeContext)) {
1128
+ return;
1129
+ }
1130
+ for (const resultErrCall of getOwnResultErrCalls(node.consequent)) {
1131
+ if (!isSkapxdResultErrCall(resultErrCall, typeContext)) {
1132
+ continue;
1133
+ }
1134
+ if (resultErrCall.arguments.length === 0) {
1135
+ continue;
1136
+ }
1137
+ if (resultErrPreservesCause(resultErrCall.arguments[0], resultGuard.name)) {
1138
+ continue;
1139
+ }
1140
+ context.report({
1141
+ data: {
1142
+ name: resultGuard.name
1143
+ },
1144
+ messageId: "missingCause",
1145
+ node: resultErrCall
1146
+ });
1147
+ }
1148
+ }
1149
+ };
1150
+ }
1151
+ };
1152
+
1153
+ // src/utils/count-own-use-state-calls-in-node.ts
1154
+ function countOwnUseStateCallsInNode(node) {
1155
+ if (!isAstNode(node)) {
1156
+ return 0;
1157
+ }
1158
+ if (isFunctionNode(node)) {
1159
+ return 0;
1160
+ }
1161
+ const ownCount = node.type === "CallExpression" && isCalleeNamed(node.callee, ["useState"]) ? 1 : 0;
1162
+ return ownCount + getNodeChildren(node).reduce(
1163
+ (total, child) => total + countOwnUseStateCallsInNode(child),
1164
+ 0
1165
+ );
1166
+ }
1167
+
1168
+ // src/utils/count-own-use-state-calls.ts
1169
+ function countOwnUseStateCalls(node) {
1170
+ const body = node.body;
1171
+ return isAstNode(body) ? countOwnUseStateCallsInNode(body) : 0;
1172
+ }
1173
+
1174
+ // src/utils/get-function-line-count.ts
1175
+ function getFunctionLineCount(node) {
1176
+ return node.loc ? node.loc.end.line - node.loc.start.line + 1 : 0;
1177
+ }
1178
+
1179
+ // src/utils/get-max-hook-size-options.ts
1180
+ function getMaxHookSizeOptions(options = {}) {
1181
+ return {
1182
+ maxLines: options.maxLines ?? 120,
1183
+ maxUseState: options.maxUseState ?? 1
1184
+ };
1185
+ }
1186
+
1187
+ // src/utils/is-hook-name.ts
1188
+ function isHookName(name) {
1189
+ return /^use[A-Z0-9]/.test(name ?? "");
1190
+ }
1191
+
1192
+ // src/rules/max-hook-size.ts
1193
+ var maxHookSize = {
1194
+ meta: {
1195
+ type: "suggestion",
1196
+ docs: {
1197
+ description: "Limita el tama\xF1o y cantidad de estados propios en hooks React."
1198
+ },
1199
+ messages: {
1200
+ tooLargeHook: "El hook `{{name}}` es demasiado grande: tiene {{lines}} lineas. Maximo permitido: {{maxLines}} lineas. Extrae efectos, handlers o flujos a hooks/archivos semanticos.",
1201
+ tooManyUseState: "El hook `{{name}}` declara {{useStateCount}} useState. Maximo permitido: {{maxUseState}}. Usa useReducer con acciones semanticas cuando varios campos cambian juntos, o extrae estado a hooks especializados."
1202
+ },
1203
+ schema: [
1204
+ {
1205
+ additionalProperties: false,
1206
+ properties: {
1207
+ maxLines: { type: "number" },
1208
+ maxUseState: { type: "number" }
1209
+ },
1210
+ type: "object"
1211
+ }
1212
+ ]
1213
+ },
1214
+ create(context) {
1215
+ const options = getMaxHookSizeOptions(context.options[0]);
1216
+ function reportIfOversizedHook(node, name, reportNode = node) {
1217
+ if (!isHookName(name)) {
1218
+ return;
1219
+ }
1220
+ const lines = getFunctionLineCount(node);
1221
+ const useStateCount = countOwnUseStateCalls(node);
1222
+ if (lines > options.maxLines) {
1223
+ context.report({
1224
+ data: {
1225
+ lines: String(lines),
1226
+ maxLines: String(options.maxLines),
1227
+ name
1228
+ },
1229
+ messageId: "tooLargeHook",
1230
+ node: reportNode
1231
+ });
1232
+ }
1233
+ if (useStateCount > options.maxUseState) {
1234
+ context.report({
1235
+ data: {
1236
+ maxUseState: String(options.maxUseState),
1237
+ name,
1238
+ useStateCount: String(useStateCount)
1239
+ },
1240
+ messageId: "tooManyUseState",
1241
+ node: reportNode
1242
+ });
1243
+ }
1244
+ }
1245
+ return {
1246
+ ArrowFunctionExpression(node) {
1247
+ reportIfOversizedHook(
1248
+ node,
1249
+ getParentFunctionName(node),
1250
+ getParentFunctionReportNode(node)
1251
+ );
1252
+ },
1253
+ FunctionDeclaration(node) {
1254
+ reportIfOversizedHook(node, node.id?.name, node.id ?? node);
1255
+ },
1256
+ FunctionExpression(node) {
1257
+ reportIfOversizedHook(
1258
+ node,
1259
+ getFunctionExpressionName(node),
1260
+ getParentFunctionReportNode(node)
1261
+ );
1262
+ }
1263
+ };
1264
+ }
1265
+ };
1266
+
1267
+ // src/utils/count-parent-segments.ts
1268
+ function countParentSegments(source) {
1269
+ let count = 0;
1270
+ for (const part of source.split("/")) {
1271
+ if (part === "..") {
1272
+ count += 1;
1273
+ continue;
1274
+ }
1275
+ if (part === ".") {
1276
+ continue;
1277
+ }
1278
+ break;
1279
+ }
1280
+ return count;
1281
+ }
1282
+
1283
+ // src/rules/no-deep-relative-imports.ts
1284
+ var noDeepRelativeImports = {
1285
+ meta: {
1286
+ type: "suggestion",
1287
+ docs: {
1288
+ description: "Limita la profundidad de los imports relativos (`../`). Empuja hacia estructuras planas o alias de ruta."
1289
+ },
1290
+ messages: {
1291
+ deepRelativeImport: "El import `{{source}}` sube {{depth}} niveles con `../`. Maximo permitido: {{maxDepth}}. Usa un alias de ruta (ej. `@/...`) o acerca el modulo a quien lo usa."
1292
+ },
1293
+ schema: [
1294
+ {
1295
+ additionalProperties: false,
1296
+ properties: {
1297
+ maxDepth: { type: "number" }
1298
+ },
1299
+ type: "object"
1300
+ }
1301
+ ]
1302
+ },
1303
+ create(context) {
1304
+ const maxDepth = context.options[0]?.maxDepth ?? 0;
1305
+ function reportIfTooDeep(source) {
1306
+ if (!source || typeof source.value !== "string") {
1307
+ return;
1308
+ }
1309
+ const depth = countParentSegments(source.value);
1310
+ if (depth <= maxDepth) {
1311
+ return;
1312
+ }
1313
+ context.report({
1314
+ data: {
1315
+ depth: String(depth),
1316
+ maxDepth: String(maxDepth),
1317
+ source: source.value
1318
+ },
1319
+ messageId: "deepRelativeImport",
1320
+ node: source
1321
+ });
1322
+ }
1323
+ return {
1324
+ ExportAllDeclaration(node) {
1325
+ reportIfTooDeep(node.source);
1326
+ },
1327
+ ExportNamedDeclaration(node) {
1328
+ reportIfTooDeep(node.source);
1329
+ },
1330
+ ImportDeclaration(node) {
1331
+ reportIfTooDeep(node.source);
1332
+ },
1333
+ ImportExpression(node) {
1334
+ reportIfTooDeep(node.source);
1335
+ }
1336
+ };
1337
+ }
1338
+ };
1339
+
1340
+ // src/rules/no-functions-inside-components.ts
1341
+ var noFunctionsInsideComponents = {
1342
+ meta: {
1343
+ type: "suggestion",
1344
+ docs: {
1345
+ description: "Prohibe definir funciones dentro de componentes React; se recrean en cada render."
1346
+ },
1347
+ messages: {
1348
+ functionInsideComponent: "No definas funciones dentro del componente `{{component}}`: se recrean en cada render. Muevela a un hook (`useX`) o a un helper fuera del componente."
1349
+ },
1350
+ schema: []
1351
+ },
1352
+ create(context) {
1353
+ function isComponentFunction(node) {
1354
+ return isFunctionNode(node) && isPascalCaseName(getFunctionName(node));
1355
+ }
1356
+ function reportIfInsideComponent(node) {
1357
+ const enclosingFunction = getContainingFunction(node);
1358
+ if (!enclosingFunction || !isComponentFunction(enclosingFunction)) {
1359
+ return;
1360
+ }
1361
+ context.report({
1362
+ data: {
1363
+ component: getFunctionName(enclosingFunction)
1364
+ },
1365
+ messageId: "functionInsideComponent",
1366
+ node
1367
+ });
1368
+ }
1369
+ return {
1370
+ ArrowFunctionExpression: reportIfInsideComponent,
1371
+ FunctionDeclaration: reportIfInsideComponent,
1372
+ FunctionExpression: reportIfInsideComponent
1373
+ };
1374
+ }
1375
+ };
1376
+
1377
+ // src/rules/no-try-catch.ts
1378
+ var noTryCatch = {
1379
+ meta: {
1380
+ type: "suggestion",
1381
+ docs: {
1382
+ description: "Prohibe try/catch; usa trySafe de @skapxd/result para modelar el error como Result."
1383
+ },
1384
+ messages: {
1385
+ noTryCatch: "Usa `trySafe` de @skapxd/result en lugar de try/catch. El error queda modelado como Result en vez de saltar como excepcion."
1386
+ },
1387
+ schema: []
1388
+ },
1389
+ create(context) {
1390
+ return {
1391
+ TryStatement(node) {
1392
+ context.report({
1393
+ messageId: "noTryCatch",
1394
+ node
1395
+ });
1396
+ }
1397
+ };
1398
+ }
1399
+ };
1400
+
1401
+ // src/rules/prefer-ts-pattern.ts
1402
+ var preferTsPattern = {
1403
+ meta: {
1404
+ type: "suggestion",
1405
+ docs: {
1406
+ description: "Prefiere match() de ts-pattern sobre switch/case y ternarios anidados."
1407
+ },
1408
+ messages: {
1409
+ noSwitch: "Usa `match()` de ts-pattern en lugar de switch/case para un control de flujo exhaustivo y tipado.",
1410
+ noNestedTernary: "Usa `match()` de ts-pattern en lugar de ternarios anidados; mejora la legibilidad y la exhaustividad."
1411
+ },
1412
+ schema: []
1413
+ },
1414
+ create(context) {
1415
+ return {
1416
+ ConditionalExpression(node) {
1417
+ if (node.parent?.type === "ConditionalExpression") {
1418
+ context.report({
1419
+ messageId: "noNestedTernary",
1420
+ node
1421
+ });
1422
+ }
1423
+ },
1424
+ SwitchStatement(node) {
1425
+ context.report({
1426
+ messageId: "noSwitch",
1427
+ node
1428
+ });
1429
+ }
1430
+ };
1431
+ }
1432
+ };
1433
+
1434
+ // src/rules/no-jsx-ternary-null.ts
1435
+ var noJsxTernaryNull = {
1436
+ meta: {
1437
+ type: "suggestion",
1438
+ docs: {
1439
+ description: "Prefiere `condicion && <Elemento />` sobre un ternario con `null` al renderizar JSX."
1440
+ },
1441
+ messages: {
1442
+ preferLogicalAnd: "Usa `condicion && elemento` en lugar de un ternario con `null` para renderizar JSX condicional."
1443
+ },
1444
+ schema: []
1445
+ },
1446
+ create(context) {
1447
+ function isNullLiteral(node) {
1448
+ return node?.type === "Literal" && node.value === null;
1449
+ }
1450
+ return {
1451
+ ConditionalExpression(node) {
1452
+ const container = node.parent;
1453
+ if (container?.type !== "JSXExpressionContainer") {
1454
+ return;
1455
+ }
1456
+ const host = container.parent;
1457
+ if (host?.type !== "JSXElement" && host?.type !== "JSXFragment") {
1458
+ return;
1459
+ }
1460
+ if (!isNullLiteral(node.alternate) && !isNullLiteral(node.consequent)) {
1461
+ return;
1462
+ }
1463
+ context.report({
1464
+ messageId: "preferLogicalAnd",
1465
+ node
1466
+ });
1467
+ }
1468
+ };
1469
+ }
1470
+ };
1471
+
1472
+ // src/utils/is-promise-type.ts
1473
+ function isPromiseType(type, typeContext) {
1474
+ return typeContext.checker.getPromisedTypeOfPromise(type) !== void 0;
1475
+ }
1476
+
1477
+ // src/rules/no-promise-chain.ts
1478
+ var defaultMethods = ["catch", "finally", "then"];
1479
+ var noPromiseChain = {
1480
+ meta: {
1481
+ type: "suggestion",
1482
+ docs: {
1483
+ description: "Prohibe encadenar .then/.catch/.finally en promesas; usa await (envuelto en trySafe)."
1484
+ },
1485
+ messages: {
1486
+ noPromiseChain: "No encadenes `.{{method}}()` en una promesa. La unica forma de tratar funciones asincronas es `await` (envuelto en `trySafe`)."
1487
+ },
1488
+ schema: [
1489
+ {
1490
+ additionalProperties: false,
1491
+ properties: {
1492
+ methods: {
1493
+ items: { type: "string" },
1494
+ type: "array"
1495
+ }
1496
+ },
1497
+ type: "object"
1498
+ }
1499
+ ]
1500
+ },
1501
+ create(context) {
1502
+ const methods = context.options[0]?.methods ?? defaultMethods;
1503
+ const typeContext = getTypeContext(context);
1504
+ return {
1505
+ CallExpression(node) {
1506
+ const callee = node.callee;
1507
+ if (callee.type !== "MemberExpression") {
1508
+ return;
1509
+ }
1510
+ const method = methods.find((name) => isMemberPropertyNamed(callee, name));
1511
+ if (!method) {
1512
+ return;
1513
+ }
1514
+ if (typeContext) {
1515
+ const type = typeContext.services.getTypeAtLocation(callee.object);
1516
+ if (!isPromiseType(type, typeContext)) {
1517
+ return;
1518
+ }
1519
+ }
1520
+ context.report({
1521
+ data: { method },
1522
+ messageId: "noPromiseChain",
1523
+ node: callee.property
1524
+ });
1525
+ }
1526
+ };
1527
+ }
1528
+ };
1529
+
1530
+ // src/shared/rules.ts
1531
+ var rules = {
1532
+ "one-root-function-per-file": oneRootFunctionPerFile,
1533
+ "jsx-return-name-pascal-case": jsxReturnNamePascalCase,
1534
+ "async-functions-return-result": asyncFunctionsReturnResult,
1535
+ "no-ad-hoc-ok-result": noAdHocOkResult,
1536
+ "await-requires-try-safe": awaitRequiresTrySafe,
1537
+ "result-error-requires-cause": resultErrorRequiresCause,
1538
+ "max-hook-size": maxHookSize,
1539
+ "no-deep-relative-imports": noDeepRelativeImports,
1540
+ "no-functions-inside-components": noFunctionsInsideComponents,
1541
+ "no-try-catch": noTryCatch,
1542
+ "prefer-ts-pattern": preferTsPattern,
1543
+ "no-jsx-ternary-null": noJsxTernaryNull,
1544
+ "no-promise-chain": noPromiseChain
1545
+ };
1546
+
1547
+ export {
1548
+ rules
1549
+ };
1550
+ //# sourceMappingURL=chunk-RP7BOODV.mjs.map