@sarj/eslint-plugin 2.6.0 → 2.7.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/dist/index.js CHANGED
@@ -2755,16 +2755,299 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
2755
2755
  }
2756
2756
  });
2757
2757
 
2758
+ // src/rules/no-silent-promise-catch.ts
2759
+ import { AST_NODE_TYPES as AST_NODE_TYPES12, ESLintUtils as ESLintUtils22 } from "@typescript-eslint/utils";
2760
+
2761
+ // src/rules/_paths.ts
2762
+ var TEST_FILE_RE = /(\.(test|spec)\.)|([\\/]__tests__[\\/])/;
2763
+ var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
2764
+ function isTestFile(filename) {
2765
+ return TEST_FILE_RE.test(filename);
2766
+ }
2767
+ function isScriptFile(filename) {
2768
+ return SCRIPT_FILE_RE.test(filename);
2769
+ }
2770
+
2771
+ // src/rules/no-silent-promise-catch.ts
2772
+ function isBodyParseCall(node) {
2773
+ return node.type === AST_NODE_TYPES12.CallExpression && node.arguments.length === 0 && node.callee.type === AST_NODE_TYPES12.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES12.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
2774
+ }
2775
+ function isSilentExpression(node) {
2776
+ switch (node.type) {
2777
+ case AST_NODE_TYPES12.Literal:
2778
+ return !("regex" in node);
2779
+ case AST_NODE_TYPES12.Identifier:
2780
+ return node.name === "undefined";
2781
+ case AST_NODE_TYPES12.UnaryExpression:
2782
+ return node.operator === "void" && node.argument.type === AST_NODE_TYPES12.Literal;
2783
+ case AST_NODE_TYPES12.ObjectExpression:
2784
+ return node.properties.length === 0;
2785
+ case AST_NODE_TYPES12.ArrayExpression:
2786
+ return node.elements.length === 0;
2787
+ case AST_NODE_TYPES12.TSAsExpression:
2788
+ return isSilentExpression(node.expression);
2789
+ default:
2790
+ return false;
2791
+ }
2792
+ }
2793
+ function isSilentHandler(handler) {
2794
+ const body = handler.body;
2795
+ if (body.type !== AST_NODE_TYPES12.BlockStatement) {
2796
+ return isSilentExpression(body);
2797
+ }
2798
+ if (body.body.length === 0) {
2799
+ return true;
2800
+ }
2801
+ if (body.body.length === 1) {
2802
+ const only = body.body[0];
2803
+ if (only !== void 0 && only.type === AST_NODE_TYPES12.ReturnStatement) {
2804
+ return only.argument === null || isSilentExpression(only.argument);
2805
+ }
2806
+ }
2807
+ return false;
2808
+ }
2809
+ var no_silent_promise_catch_default = ESLintUtils22.RuleCreator(
2810
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2811
+ )({
2812
+ name: "no-silent-promise-catch",
2813
+ meta: {
2814
+ type: "problem",
2815
+ docs: {
2816
+ description: "Disallow `.catch()` handlers that silently swallow the rejection (e.g. `.catch(() => null)`); log, rethrow, or handle the error."
2817
+ },
2818
+ schema: [],
2819
+ messages: {
2820
+ silentCatch: "This `.catch()` swallows the rejection without logging, rethrowing, or handling it \u2014 failures become invisible and callers get an indistinguishable sentinel. Log the error (and only then map to a fallback), or let it propagate."
2821
+ }
2822
+ },
2823
+ defaultOptions: [],
2824
+ create(context) {
2825
+ if (isTestFile(context.filename)) {
2826
+ return {};
2827
+ }
2828
+ return {
2829
+ CallExpression(node) {
2830
+ if (node.callee.type !== AST_NODE_TYPES12.MemberExpression || node.callee.computed || node.callee.property.type !== AST_NODE_TYPES12.Identifier || node.callee.property.name !== "catch") {
2831
+ return;
2832
+ }
2833
+ if (isBodyParseCall(node.callee.object)) {
2834
+ return;
2835
+ }
2836
+ if (node.arguments.length !== 1) {
2837
+ return;
2838
+ }
2839
+ const handler = node.arguments[0];
2840
+ if (handler === void 0 || handler.type !== AST_NODE_TYPES12.ArrowFunctionExpression && handler.type !== AST_NODE_TYPES12.FunctionExpression) {
2841
+ return;
2842
+ }
2843
+ if (isSilentHandler(handler)) {
2844
+ context.report({ node, messageId: "silentCatch" });
2845
+ }
2846
+ }
2847
+ };
2848
+ }
2849
+ });
2850
+
2851
+ // src/rules/require-fetch-timeout.ts
2852
+ import {
2853
+ AST_NODE_TYPES as AST_NODE_TYPES13,
2854
+ ASTUtils,
2855
+ ESLintUtils as ESLintUtils23
2856
+ } from "@typescript-eslint/utils";
2857
+ var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
2858
+ "globalThis",
2859
+ "window",
2860
+ "self"
2861
+ ]);
2862
+ function matchesAnyPattern2(filename, patterns) {
2863
+ for (const pattern of patterns) {
2864
+ const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
2865
+ if (new RegExp(`^${regexSource}$`).test(filename)) {
2866
+ return true;
2867
+ }
2868
+ }
2869
+ return false;
2870
+ }
2871
+ function initProvablyLacksSignal(init) {
2872
+ if (init.type !== AST_NODE_TYPES13.ObjectExpression) {
2873
+ return false;
2874
+ }
2875
+ for (const prop of init.properties) {
2876
+ if (prop.type === AST_NODE_TYPES13.SpreadElement) {
2877
+ return false;
2878
+ }
2879
+ if (prop.key.type === AST_NODE_TYPES13.Identifier && prop.key.name === "signal" || prop.key.type === AST_NODE_TYPES13.Literal && prop.key.value === "signal") {
2880
+ return false;
2881
+ }
2882
+ if (prop.computed) {
2883
+ return false;
2884
+ }
2885
+ }
2886
+ return true;
2887
+ }
2888
+ function isStringish(node) {
2889
+ return node.type === AST_NODE_TYPES13.Literal && typeof node.value === "string" || node.type === AST_NODE_TYPES13.TemplateLiteral;
2890
+ }
2891
+ var require_fetch_timeout_default = ESLintUtils23.RuleCreator(
2892
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2893
+ )({
2894
+ name: "require-fetch-timeout",
2895
+ meta: {
2896
+ type: "problem",
2897
+ docs: {
2898
+ description: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever."
2899
+ },
2900
+ schema: [
2901
+ {
2902
+ type: "object",
2903
+ additionalProperties: false,
2904
+ properties: {
2905
+ allowIn: {
2906
+ description: "Glob patterns for wrapper modules exempt from the rule. Matched against the ABSOLUTE file path, so anchor with a `**/` prefix (e.g. `**/http-client.ts`).",
2907
+ type: "array",
2908
+ items: { type: "string" }
2909
+ }
2910
+ }
2911
+ }
2912
+ ],
2913
+ messages: {
2914
+ missingSignal: "This `fetch()` has no abort `signal` \u2014 a stalled upstream will hang it forever. Pass `{ signal: AbortSignal.timeout(ms) }` or a signal from an AbortController."
2915
+ }
2916
+ },
2917
+ defaultOptions: [{}],
2918
+ create(context, [optionsArg]) {
2919
+ if (isTestFile(context.filename) || isScriptFile(context.filename)) {
2920
+ return {};
2921
+ }
2922
+ const allowIn = optionsArg?.allowIn ?? [];
2923
+ if (allowIn.length > 0 && matchesAnyPattern2(context.filename, allowIn)) {
2924
+ return {};
2925
+ }
2926
+ function resolvesToGlobal(identifier) {
2927
+ const scope = context.sourceCode.getScope(identifier);
2928
+ const variable = ASTUtils.findVariable(scope, identifier.name);
2929
+ return variable === null || variable.defs.length === 0;
2930
+ }
2931
+ function isGlobalFetchCall(callee) {
2932
+ if (callee.type === AST_NODE_TYPES13.Identifier) {
2933
+ return callee.name === "fetch" && resolvesToGlobal(callee);
2934
+ }
2935
+ return callee.type === AST_NODE_TYPES13.MemberExpression && !callee.computed && callee.property.type === AST_NODE_TYPES13.Identifier && callee.property.name === "fetch" && callee.object.type === AST_NODE_TYPES13.Identifier && GLOBAL_OBJECTS.has(callee.object.name) && resolvesToGlobal(callee.object);
2936
+ }
2937
+ return {
2938
+ CallExpression(node) {
2939
+ if (!isGlobalFetchCall(node.callee)) {
2940
+ return;
2941
+ }
2942
+ const [first, init] = node.arguments;
2943
+ if (node.arguments.length === 1 && first !== void 0 && !isStringish(first)) {
2944
+ return;
2945
+ }
2946
+ if (init === void 0 || initProvablyLacksSignal(init)) {
2947
+ context.report({ node, messageId: "missingSignal" });
2948
+ }
2949
+ }
2950
+ };
2951
+ }
2952
+ });
2953
+
2954
+ // src/rules/require-schema-validate-search.ts
2955
+ import { AST_NODE_TYPES as AST_NODE_TYPES14, ESLintUtils as ESLintUtils24 } from "@typescript-eslint/utils";
2956
+ var VALIDATOR_METHODS = /* @__PURE__ */ new Set([
2957
+ "parse",
2958
+ "safeParse",
2959
+ "decode"
2960
+ ]);
2961
+ function isConstTypeAnnotation(typeAnnotation) {
2962
+ return typeAnnotation.type === AST_NODE_TYPES14.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES14.Identifier && typeAnnotation.typeName.name === "const";
2963
+ }
2964
+ function isValidatorCall(node) {
2965
+ return node.callee.type === AST_NODE_TYPES14.MemberExpression && !node.callee.computed && node.callee.property.type === AST_NODE_TYPES14.Identifier && VALIDATOR_METHODS.has(node.callee.property.name);
2966
+ }
2967
+ function findCastExpression(node, insideValidatorArg) {
2968
+ if ((node.type === AST_NODE_TYPES14.TSAsExpression || node.type === AST_NODE_TYPES14.TSTypeAssertion) && !isConstTypeAnnotation(node.typeAnnotation) && !insideValidatorArg) {
2969
+ return node;
2970
+ }
2971
+ if (node.type === AST_NODE_TYPES14.CallExpression && isValidatorCall(node)) {
2972
+ const inCallee = findCastExpression(node.callee, insideValidatorArg);
2973
+ if (inCallee !== null) {
2974
+ return inCallee;
2975
+ }
2976
+ for (const arg of node.arguments) {
2977
+ const found = findCastExpression(arg, true);
2978
+ if (found !== null) {
2979
+ return found;
2980
+ }
2981
+ }
2982
+ return null;
2983
+ }
2984
+ for (const key of Object.keys(node)) {
2985
+ if (key === "parent") {
2986
+ continue;
2987
+ }
2988
+ const value = node[key];
2989
+ const children = Array.isArray(value) ? value : [value];
2990
+ for (const child of children) {
2991
+ if (child !== null && typeof child === "object" && "type" in child && typeof child.type === "string") {
2992
+ const found = findCastExpression(
2993
+ child,
2994
+ insideValidatorArg
2995
+ );
2996
+ if (found !== null) {
2997
+ return found;
2998
+ }
2999
+ }
3000
+ }
3001
+ }
3002
+ return null;
3003
+ }
3004
+ var require_schema_validate_search_default = ESLintUtils24.RuleCreator(
3005
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3006
+ )({
3007
+ name: "require-schema-validate-search",
3008
+ meta: {
3009
+ type: "problem",
3010
+ docs: {
3011
+ description: "Disallow `as` casts inside hand-rolled `validateSearch` functions; use a schema validator (e.g. zodValidator) so search params are validated at runtime."
3012
+ },
3013
+ schema: [],
3014
+ messages: {
3015
+ castInValidateSearch: "This `validateSearch` asserts the search-param shape with `as` instead of validating it \u2014 malformed query params flow through typed as clean data. Use a schema validator (e.g. `zodValidator(searchSchema)` or `searchSchema.parse`) instead of casting."
3016
+ }
3017
+ },
3018
+ defaultOptions: [],
3019
+ create(context) {
3020
+ if (isTestFile(context.filename)) {
3021
+ return {};
3022
+ }
3023
+ return {
3024
+ Property(node) {
3025
+ const isValidateSearchKey = !node.computed && node.key.type === AST_NODE_TYPES14.Identifier && node.key.name === "validateSearch" || node.key.type === AST_NODE_TYPES14.Literal && node.key.value === "validateSearch";
3026
+ if (!isValidateSearchKey) {
3027
+ return;
3028
+ }
3029
+ if (node.value.type !== AST_NODE_TYPES14.ArrowFunctionExpression && node.value.type !== AST_NODE_TYPES14.FunctionExpression) {
3030
+ return;
3031
+ }
3032
+ const cast = findCastExpression(node.value.body, false);
3033
+ if (cast !== null) {
3034
+ context.report({ node: cast, messageId: "castInValidateSearch" });
3035
+ }
3036
+ }
3037
+ };
3038
+ }
3039
+ });
3040
+
2758
3041
  // src/rules/no-fat-try-blocks.ts
2759
3042
  import {
2760
- ESLintUtils as ESLintUtils22,
2761
- AST_NODE_TYPES as AST_NODE_TYPES12
3043
+ ESLintUtils as ESLintUtils25,
3044
+ AST_NODE_TYPES as AST_NODE_TYPES15
2762
3045
  } from "@typescript-eslint/utils";
2763
3046
  var MAX_TRY_BODY_STATEMENTS = 3;
2764
3047
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2765
- AST_NODE_TYPES12.FunctionDeclaration,
2766
- AST_NODE_TYPES12.FunctionExpression,
2767
- AST_NODE_TYPES12.ArrowFunctionExpression
3048
+ AST_NODE_TYPES15.FunctionDeclaration,
3049
+ AST_NODE_TYPES15.FunctionExpression,
3050
+ AST_NODE_TYPES15.ArrowFunctionExpression
2768
3051
  ]);
2769
3052
  var PURE_METHODS = /* @__PURE__ */ new Set([
2770
3053
  "map",
@@ -2862,20 +3145,20 @@ function isNode4(value) {
2862
3145
  }
2863
3146
  function isPureCall(node) {
2864
3147
  const callee = node.callee;
2865
- if (callee.type !== AST_NODE_TYPES12.MemberExpression) {
3148
+ if (callee.type !== AST_NODE_TYPES15.MemberExpression) {
2866
3149
  return false;
2867
3150
  }
2868
3151
  const property = callee.property;
2869
- if (property.type !== AST_NODE_TYPES12.Identifier) {
3152
+ if (property.type !== AST_NODE_TYPES15.Identifier) {
2870
3153
  return false;
2871
3154
  }
2872
- if (callee.object.type === AST_NODE_TYPES12.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
3155
+ if (callee.object.type === AST_NODE_TYPES15.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2873
3156
  return true;
2874
3157
  }
2875
3158
  return PURE_METHODS.has(property.name);
2876
3159
  }
2877
3160
  function isPureNew(node) {
2878
- return node.callee.type === AST_NODE_TYPES12.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
3161
+ return node.callee.type === AST_NODE_TYPES15.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2879
3162
  }
2880
3163
  function subtreeMatches(stmt, predicate) {
2881
3164
  let found = false;
@@ -2912,14 +3195,14 @@ function subtreeMatches(stmt, predicate) {
2912
3195
  visit(stmt);
2913
3196
  return found;
2914
3197
  }
2915
- var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES12.AwaitExpression);
3198
+ var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === AST_NODE_TYPES15.AwaitExpression);
2916
3199
  var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2917
3200
  stmt,
2918
- (n) => n.type === AST_NODE_TYPES12.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES12.NewExpression && !isPureNew(n)
3201
+ (n) => n.type === AST_NODE_TYPES15.CallExpression && !isPureCall(n) || n.type === AST_NODE_TYPES15.NewExpression && !isPureNew(n)
2919
3202
  );
2920
3203
  function unwrap2(expr) {
2921
3204
  let current = expr;
2922
- while (current.type === AST_NODE_TYPES12.ChainExpression || current.type === AST_NODE_TYPES12.TSNonNullExpression) {
3205
+ while (current.type === AST_NODE_TYPES15.ChainExpression || current.type === AST_NODE_TYPES15.TSNonNullExpression) {
2923
3206
  current = current.expression;
2924
3207
  }
2925
3208
  return current;
@@ -2928,7 +3211,7 @@ function canThrow(stmt) {
2928
3211
  if (hasAwait(stmt)) {
2929
3212
  return true;
2930
3213
  }
2931
- if (stmt.type === AST_NODE_TYPES12.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES12.CallExpression) {
3214
+ if (stmt.type === AST_NODE_TYPES15.ExpressionStatement && unwrap2(stmt.expression).type === AST_NODE_TYPES15.CallExpression) {
2932
3215
  return false;
2933
3216
  }
2934
3217
  return hasThrowingCallOrNew(stmt);
@@ -2939,9 +3222,9 @@ function handlerRethrows(handler) {
2939
3222
  }
2940
3223
  const body = handler.body.body;
2941
3224
  const last = body[body.length - 1];
2942
- return last !== void 0 && last.type === AST_NODE_TYPES12.ThrowStatement;
3225
+ return last !== void 0 && last.type === AST_NODE_TYPES15.ThrowStatement;
2943
3226
  }
2944
- var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
3227
+ var no_fat_try_blocks_default = ESLintUtils25.RuleCreator(
2945
3228
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2946
3229
  )({
2947
3230
  name: "no-fat-try-blocks",
@@ -2982,7 +3265,7 @@ var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2982
3265
  });
2983
3266
 
2984
3267
  // src/rules/no-secret-in-log.ts
2985
- import { ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
3268
+ import { ESLintUtils as ESLintUtils26 } from "@typescript-eslint/utils";
2986
3269
  var LOG_METHODS2 = /* @__PURE__ */ new Set([
2987
3270
  "debug",
2988
3271
  "info",
@@ -3180,7 +3463,7 @@ function propertyKeyName2(prop) {
3180
3463
  }
3181
3464
  return null;
3182
3465
  }
3183
- var no_secret_in_log_default = ESLintUtils23.RuleCreator(
3466
+ var no_secret_in_log_default = ESLintUtils26.RuleCreator(
3184
3467
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3185
3468
  )({
3186
3469
  name: "no-secret-in-log",
@@ -3248,15 +3531,15 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
3248
3531
  });
3249
3532
 
3250
3533
  // src/rules/no-unsafe-cast.ts
3251
- import { ESLintUtils as ESLintUtils24 } from "@typescript-eslint/utils";
3252
- import { AST_NODE_TYPES as AST_NODE_TYPES13 } from "@typescript-eslint/utils";
3534
+ import { ESLintUtils as ESLintUtils27 } from "@typescript-eslint/utils";
3535
+ import { AST_NODE_TYPES as AST_NODE_TYPES16 } from "@typescript-eslint/utils";
3253
3536
  function isAnyAnnotation(node) {
3254
- return node.type === AST_NODE_TYPES13.TSAnyKeyword;
3537
+ return node.type === AST_NODE_TYPES16.TSAnyKeyword;
3255
3538
  }
3256
3539
  function isConstAssertion(typeAnnotation) {
3257
- return typeAnnotation.type === AST_NODE_TYPES13.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES13.Identifier && typeAnnotation.typeName.name === "const";
3540
+ return typeAnnotation.type === AST_NODE_TYPES16.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES16.Identifier && typeAnnotation.typeName.name === "const";
3258
3541
  }
3259
- var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3542
+ var no_unsafe_cast_default = ESLintUtils27.RuleCreator(
3260
3543
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3261
3544
  )({
3262
3545
  name: "no-unsafe-cast",
@@ -3282,7 +3565,7 @@ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3282
3565
  return;
3283
3566
  }
3284
3567
  const inner = node.expression;
3285
- if (inner.type === AST_NODE_TYPES13.TSAsExpression || inner.type === AST_NODE_TYPES13.TSTypeAssertion) {
3568
+ if (inner.type === AST_NODE_TYPES16.TSAsExpression || inner.type === AST_NODE_TYPES16.TSTypeAssertion) {
3286
3569
  context.report({ node, messageId: "doubleCast" });
3287
3570
  }
3288
3571
  }
@@ -3295,8 +3578,8 @@ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3295
3578
 
3296
3579
  // src/rules/prefer-string-literal-union.ts
3297
3580
  import {
3298
- ESLintUtils as ESLintUtils25,
3299
- AST_NODE_TYPES as AST_NODE_TYPES14
3581
+ ESLintUtils as ESLintUtils28,
3582
+ AST_NODE_TYPES as AST_NODE_TYPES17
3300
3583
  } from "@typescript-eslint/utils";
3301
3584
  import * as ts from "typescript";
3302
3585
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
@@ -3340,19 +3623,19 @@ function isChoiceLikeName(name) {
3340
3623
  return CHOICE_TOKENS.has(lastWord(name));
3341
3624
  }
3342
3625
  function keyName(key) {
3343
- if (key.type === AST_NODE_TYPES14.Identifier) {
3626
+ if (key.type === AST_NODE_TYPES17.Identifier) {
3344
3627
  return key.name;
3345
3628
  }
3346
- if (key.type === AST_NODE_TYPES14.Literal && typeof key.value === "string") {
3629
+ if (key.type === AST_NODE_TYPES17.Literal && typeof key.value === "string") {
3347
3630
  return key.value;
3348
3631
  }
3349
3632
  return null;
3350
3633
  }
3351
3634
  function isStringLiteralMember(t) {
3352
- return t.type === AST_NODE_TYPES14.TSLiteralType && t.literal.type === AST_NODE_TYPES14.Literal && typeof t.literal.value === "string";
3635
+ return t.type === AST_NODE_TYPES17.TSLiteralType && t.literal.type === AST_NODE_TYPES17.Literal && typeof t.literal.value === "string";
3353
3636
  }
3354
3637
  function isStringLiteralUnion(node) {
3355
- if (node?.type !== AST_NODE_TYPES14.TSUnionType) {
3638
+ if (node?.type !== AST_NODE_TYPES17.TSUnionType) {
3356
3639
  return false;
3357
3640
  }
3358
3641
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -3381,12 +3664,12 @@ function bindingSourceExpression(decl) {
3381
3664
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
3382
3665
  }
3383
3666
  function refKey(node) {
3384
- if (node.type === AST_NODE_TYPES14.Identifier) {
3667
+ if (node.type === AST_NODE_TYPES17.Identifier) {
3385
3668
  return node.name;
3386
3669
  }
3387
- if (node.type === AST_NODE_TYPES14.MemberExpression && !node.computed) {
3670
+ if (node.type === AST_NODE_TYPES17.MemberExpression && !node.computed) {
3388
3671
  const inner = refKey(node.object);
3389
- if (inner === null || node.property.type !== AST_NODE_TYPES14.Identifier) {
3672
+ if (inner === null || node.property.type !== AST_NODE_TYPES17.Identifier) {
3390
3673
  return null;
3391
3674
  }
3392
3675
  return `${inner}.${node.property.name}`;
@@ -3394,12 +3677,12 @@ function refKey(node) {
3394
3677
  return null;
3395
3678
  }
3396
3679
  function strLiteral(node) {
3397
- if (node.type === AST_NODE_TYPES14.Literal && typeof node.value === "string") {
3680
+ if (node.type === AST_NODE_TYPES17.Literal && typeof node.value === "string") {
3398
3681
  return node.value;
3399
3682
  }
3400
3683
  return null;
3401
3684
  }
3402
- var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3685
+ var prefer_string_literal_union_default = ESLintUtils28.RuleCreator(
3403
3686
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3404
3687
  )({
3405
3688
  name: "prefer-string-literal-union",
@@ -3423,7 +3706,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3423
3706
  }
3424
3707
  let services;
3425
3708
  try {
3426
- services = ESLintUtils25.getParserServices(context);
3709
+ services = ESLintUtils28.getParserServices(context);
3427
3710
  } catch {
3428
3711
  services = null;
3429
3712
  }
@@ -3507,7 +3790,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3507
3790
  containersWithUnion.add(container);
3508
3791
  return;
3509
3792
  }
3510
- if (typeNode?.type !== AST_NODE_TYPES14.TSStringKeyword) {
3793
+ if (typeNode?.type !== AST_NODE_TYPES17.TSStringKeyword) {
3511
3794
  return;
3512
3795
  }
3513
3796
  const name = keyName(key);
@@ -3595,10 +3878,10 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3595
3878
  }
3596
3879
  };
3597
3880
  function refKeyText(node) {
3598
- if (node.type === AST_NODE_TYPES14.BinaryExpression) {
3881
+ if (node.type === AST_NODE_TYPES17.BinaryExpression) {
3599
3882
  return refKey(node.left) ?? refKey(node.right) ?? "value";
3600
3883
  }
3601
- if (node.type === AST_NODE_TYPES14.SwitchStatement) {
3884
+ if (node.type === AST_NODE_TYPES17.SwitchStatement) {
3602
3885
  return refKey(node.discriminant) ?? "value";
3603
3886
  }
3604
3887
  return "value";
@@ -3607,7 +3890,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3607
3890
  });
3608
3891
 
3609
3892
  // src/rules/single-public-export.ts
3610
- import { ESLintUtils as ESLintUtils26, AST_NODE_TYPES as AST_NODE_TYPES15 } from "@typescript-eslint/utils";
3893
+ import { ESLintUtils as ESLintUtils29, AST_NODE_TYPES as AST_NODE_TYPES18 } from "@typescript-eslint/utils";
3611
3894
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
3612
3895
  "util",
3613
3896
  "utils",
@@ -3630,7 +3913,7 @@ var ACRONYM_OVERRIDES = [
3630
3913
  [/gRPC/g, "Grpc"]
3631
3914
  ];
3632
3915
  var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
3633
- var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3916
+ var TEST_FILE_RE2 = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3634
3917
  var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
3635
3918
  var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
3636
3919
  var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
@@ -3641,12 +3924,12 @@ var kebabCase2 = (name) => {
3641
3924
  }
3642
3925
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
3643
3926
  };
3644
- var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES15.ArrowFunctionExpression || node.type === AST_NODE_TYPES15.FunctionExpression);
3927
+ var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES18.ArrowFunctionExpression || node.type === AST_NODE_TYPES18.FunctionExpression);
3645
3928
  var functionConstName = (decl) => {
3646
3929
  if (decl.declarations.length !== 1) return null;
3647
3930
  const [declarator] = decl.declarations;
3648
3931
  if (declarator === void 0) return null;
3649
- if (declarator.id.type !== AST_NODE_TYPES15.Identifier) return null;
3932
+ if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return null;
3650
3933
  if (!isFunctionExpression(declarator.init)) return null;
3651
3934
  return declarator.id.name;
3652
3935
  };
@@ -3660,20 +3943,20 @@ var summarizeExports = (body) => {
3660
3943
  };
3661
3944
  for (const statement of body) {
3662
3945
  switch (statement.type) {
3663
- case AST_NODE_TYPES15.ExportAllDeclaration:
3946
+ case AST_NODE_TYPES18.ExportAllDeclaration:
3664
3947
  hasReExport = true;
3665
3948
  break;
3666
- case AST_NODE_TYPES15.ExportDefaultDeclaration: {
3949
+ case AST_NODE_TYPES18.ExportDefaultDeclaration: {
3667
3950
  names += 1;
3668
3951
  const decl = statement.declaration;
3669
- if (decl.type === AST_NODE_TYPES15.FunctionDeclaration && decl.id !== null) {
3952
+ if (decl.type === AST_NODE_TYPES18.FunctionDeclaration && decl.id !== null) {
3670
3953
  candidate = { name: decl.id.name, node: statement };
3671
- } else if (decl.type === AST_NODE_TYPES15.ClassDeclaration && decl.id !== null) {
3954
+ } else if (decl.type === AST_NODE_TYPES18.ClassDeclaration && decl.id !== null) {
3672
3955
  candidate = { name: decl.id.name, node: statement };
3673
3956
  }
3674
3957
  break;
3675
3958
  }
3676
- case AST_NODE_TYPES15.ExportNamedDeclaration: {
3959
+ case AST_NODE_TYPES18.ExportNamedDeclaration: {
3677
3960
  if (statement.source !== null) {
3678
3961
  hasReExport = true;
3679
3962
  break;
@@ -3684,15 +3967,15 @@ var summarizeExports = (body) => {
3684
3967
  break;
3685
3968
  }
3686
3969
  switch (decl.type) {
3687
- case AST_NODE_TYPES15.FunctionDeclaration:
3970
+ case AST_NODE_TYPES18.FunctionDeclaration:
3688
3971
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3689
3972
  else names += 1;
3690
3973
  break;
3691
- case AST_NODE_TYPES15.ClassDeclaration:
3974
+ case AST_NODE_TYPES18.ClassDeclaration:
3692
3975
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3693
3976
  else names += 1;
3694
3977
  break;
3695
- case AST_NODE_TYPES15.VariableDeclaration: {
3978
+ case AST_NODE_TYPES18.VariableDeclaration: {
3696
3979
  const fnName = functionConstName(decl);
3697
3980
  if (fnName !== null && decl.declarations.length === 1) {
3698
3981
  addCandidate(fnName, statement);
@@ -3712,7 +3995,7 @@ var summarizeExports = (body) => {
3712
3995
  }
3713
3996
  return { names, hasReExport, candidate };
3714
3997
  };
3715
- var single_public_export_default = ESLintUtils26.RuleCreator(
3998
+ var single_public_export_default = ESLintUtils29.RuleCreator(
3716
3999
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3717
4000
  )({
3718
4001
  name: "single-public-export",
@@ -3730,7 +4013,7 @@ var single_public_export_default = ESLintUtils26.RuleCreator(
3730
4013
  create(context) {
3731
4014
  const base = basename(context.filename);
3732
4015
  if (base.endsWith(".d.ts")) return {};
3733
- if (TEST_FILE_RE.test(base)) return {};
4016
+ if (TEST_FILE_RE2.test(base)) return {};
3734
4017
  const stem = stemOf(base);
3735
4018
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
3736
4019
  return {
@@ -3778,12 +4061,15 @@ var rules = {
3778
4061
  "no-secret-in-log": no_secret_in_log_default,
3779
4062
  "no-unsafe-cast": no_unsafe_cast_default,
3780
4063
  "prefer-string-literal-union": prefer_string_literal_union_default,
3781
- "single-public-export": single_public_export_default
4064
+ "single-public-export": single_public_export_default,
4065
+ "no-silent-promise-catch": no_silent_promise_catch_default,
4066
+ "require-fetch-timeout": require_fetch_timeout_default,
4067
+ "require-schema-validate-search": require_schema_validate_search_default
3782
4068
  };
3783
4069
  var plugin = {
3784
4070
  meta: {
3785
4071
  name: "@sarj/eslint-plugin",
3786
- version: "2.6.0"
4072
+ version: "2.7.0"
3787
4073
  },
3788
4074
  rules,
3789
4075
  configs: {
@@ -3815,7 +4101,11 @@ var plugin = {
3815
4101
  "@sarj/no-secret-in-log": "warn",
3816
4102
  "@sarj/no-unsafe-cast": "warn",
3817
4103
  "@sarj/single-public-export": "warn",
3818
- "@sarj/prefer-string-literal-union": "warn"
4104
+ "@sarj/prefer-string-literal-union": "warn",
4105
+ // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
4106
+ "@sarj/require-fetch-timeout": "warn",
4107
+ "@sarj/no-silent-promise-catch": "warn",
4108
+ "@sarj/require-schema-validate-search": "warn"
3819
4109
  }
3820
4110
  },
3821
4111
  strict: {
@@ -3851,7 +4141,11 @@ var plugin = {
3851
4141
  "@sarj/no-unsafe-cast": "warn",
3852
4142
  "@sarj/single-public-export": "error",
3853
4143
  // High-volume/stylistic — warn until rollout proves FP rate.
3854
- "@sarj/prefer-string-literal-union": "warn"
4144
+ "@sarj/prefer-string-literal-union": "warn",
4145
+ // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
4146
+ "@sarj/require-fetch-timeout": "error",
4147
+ "@sarj/no-silent-promise-catch": "error",
4148
+ "@sarj/require-schema-validate-search": "error"
3855
4149
  }
3856
4150
  }
3857
4151
  }