@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.cjs CHANGED
@@ -2774,13 +2774,292 @@ var no_cors_wildcard_with_credentials_default = import_utils23.ESLintUtils.RuleC
2774
2774
  }
2775
2775
  });
2776
2776
 
2777
- // src/rules/no-fat-try-blocks.ts
2777
+ // src/rules/no-silent-promise-catch.ts
2778
2778
  var import_utils24 = require("@typescript-eslint/utils");
2779
+
2780
+ // src/rules/_paths.ts
2781
+ var TEST_FILE_RE = /(\.(test|spec)\.)|([\\/]__tests__[\\/])/;
2782
+ var SCRIPT_FILE_RE = /([\\/]scripts[\\/])|(\.mjs$)/;
2783
+ function isTestFile(filename) {
2784
+ return TEST_FILE_RE.test(filename);
2785
+ }
2786
+ function isScriptFile(filename) {
2787
+ return SCRIPT_FILE_RE.test(filename);
2788
+ }
2789
+
2790
+ // src/rules/no-silent-promise-catch.ts
2791
+ function isBodyParseCall(node) {
2792
+ return node.type === import_utils24.AST_NODE_TYPES.CallExpression && node.arguments.length === 0 && node.callee.type === import_utils24.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils24.AST_NODE_TYPES.Identifier && (node.callee.property.name === "json" || node.callee.property.name === "text");
2793
+ }
2794
+ function isSilentExpression(node) {
2795
+ switch (node.type) {
2796
+ case import_utils24.AST_NODE_TYPES.Literal:
2797
+ return !("regex" in node);
2798
+ case import_utils24.AST_NODE_TYPES.Identifier:
2799
+ return node.name === "undefined";
2800
+ case import_utils24.AST_NODE_TYPES.UnaryExpression:
2801
+ return node.operator === "void" && node.argument.type === import_utils24.AST_NODE_TYPES.Literal;
2802
+ case import_utils24.AST_NODE_TYPES.ObjectExpression:
2803
+ return node.properties.length === 0;
2804
+ case import_utils24.AST_NODE_TYPES.ArrayExpression:
2805
+ return node.elements.length === 0;
2806
+ case import_utils24.AST_NODE_TYPES.TSAsExpression:
2807
+ return isSilentExpression(node.expression);
2808
+ default:
2809
+ return false;
2810
+ }
2811
+ }
2812
+ function isSilentHandler(handler) {
2813
+ const body = handler.body;
2814
+ if (body.type !== import_utils24.AST_NODE_TYPES.BlockStatement) {
2815
+ return isSilentExpression(body);
2816
+ }
2817
+ if (body.body.length === 0) {
2818
+ return true;
2819
+ }
2820
+ if (body.body.length === 1) {
2821
+ const only = body.body[0];
2822
+ if (only !== void 0 && only.type === import_utils24.AST_NODE_TYPES.ReturnStatement) {
2823
+ return only.argument === null || isSilentExpression(only.argument);
2824
+ }
2825
+ }
2826
+ return false;
2827
+ }
2828
+ var no_silent_promise_catch_default = import_utils24.ESLintUtils.RuleCreator(
2829
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2830
+ )({
2831
+ name: "no-silent-promise-catch",
2832
+ meta: {
2833
+ type: "problem",
2834
+ docs: {
2835
+ description: "Disallow `.catch()` handlers that silently swallow the rejection (e.g. `.catch(() => null)`); log, rethrow, or handle the error."
2836
+ },
2837
+ schema: [],
2838
+ messages: {
2839
+ 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."
2840
+ }
2841
+ },
2842
+ defaultOptions: [],
2843
+ create(context) {
2844
+ if (isTestFile(context.filename)) {
2845
+ return {};
2846
+ }
2847
+ return {
2848
+ CallExpression(node) {
2849
+ if (node.callee.type !== import_utils24.AST_NODE_TYPES.MemberExpression || node.callee.computed || node.callee.property.type !== import_utils24.AST_NODE_TYPES.Identifier || node.callee.property.name !== "catch") {
2850
+ return;
2851
+ }
2852
+ if (isBodyParseCall(node.callee.object)) {
2853
+ return;
2854
+ }
2855
+ if (node.arguments.length !== 1) {
2856
+ return;
2857
+ }
2858
+ const handler = node.arguments[0];
2859
+ if (handler === void 0 || handler.type !== import_utils24.AST_NODE_TYPES.ArrowFunctionExpression && handler.type !== import_utils24.AST_NODE_TYPES.FunctionExpression) {
2860
+ return;
2861
+ }
2862
+ if (isSilentHandler(handler)) {
2863
+ context.report({ node, messageId: "silentCatch" });
2864
+ }
2865
+ }
2866
+ };
2867
+ }
2868
+ });
2869
+
2870
+ // src/rules/require-fetch-timeout.ts
2871
+ var import_utils25 = require("@typescript-eslint/utils");
2872
+ var GLOBAL_OBJECTS = /* @__PURE__ */ new Set([
2873
+ "globalThis",
2874
+ "window",
2875
+ "self"
2876
+ ]);
2877
+ function matchesAnyPattern2(filename, patterns) {
2878
+ for (const pattern of patterns) {
2879
+ const regexSource = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLESTAR::").replace(/\*/g, "[^/\\\\]*").replace(/::DOUBLESTAR::/g, ".*");
2880
+ if (new RegExp(`^${regexSource}$`).test(filename)) {
2881
+ return true;
2882
+ }
2883
+ }
2884
+ return false;
2885
+ }
2886
+ function initProvablyLacksSignal(init) {
2887
+ if (init.type !== import_utils25.AST_NODE_TYPES.ObjectExpression) {
2888
+ return false;
2889
+ }
2890
+ for (const prop of init.properties) {
2891
+ if (prop.type === import_utils25.AST_NODE_TYPES.SpreadElement) {
2892
+ return false;
2893
+ }
2894
+ if (prop.key.type === import_utils25.AST_NODE_TYPES.Identifier && prop.key.name === "signal" || prop.key.type === import_utils25.AST_NODE_TYPES.Literal && prop.key.value === "signal") {
2895
+ return false;
2896
+ }
2897
+ if (prop.computed) {
2898
+ return false;
2899
+ }
2900
+ }
2901
+ return true;
2902
+ }
2903
+ function isStringish(node) {
2904
+ return node.type === import_utils25.AST_NODE_TYPES.Literal && typeof node.value === "string" || node.type === import_utils25.AST_NODE_TYPES.TemplateLiteral;
2905
+ }
2906
+ var require_fetch_timeout_default = import_utils25.ESLintUtils.RuleCreator(
2907
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
2908
+ )({
2909
+ name: "require-fetch-timeout",
2910
+ meta: {
2911
+ type: "problem",
2912
+ docs: {
2913
+ description: "Require an abort `signal` (e.g. `AbortSignal.timeout(ms)`) on global `fetch()` calls so stalled upstreams cannot hang the caller forever."
2914
+ },
2915
+ schema: [
2916
+ {
2917
+ type: "object",
2918
+ additionalProperties: false,
2919
+ properties: {
2920
+ allowIn: {
2921
+ 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`).",
2922
+ type: "array",
2923
+ items: { type: "string" }
2924
+ }
2925
+ }
2926
+ }
2927
+ ],
2928
+ messages: {
2929
+ 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."
2930
+ }
2931
+ },
2932
+ defaultOptions: [{}],
2933
+ create(context, [optionsArg]) {
2934
+ if (isTestFile(context.filename) || isScriptFile(context.filename)) {
2935
+ return {};
2936
+ }
2937
+ const allowIn = optionsArg?.allowIn ?? [];
2938
+ if (allowIn.length > 0 && matchesAnyPattern2(context.filename, allowIn)) {
2939
+ return {};
2940
+ }
2941
+ function resolvesToGlobal(identifier) {
2942
+ const scope = context.sourceCode.getScope(identifier);
2943
+ const variable = import_utils25.ASTUtils.findVariable(scope, identifier.name);
2944
+ return variable === null || variable.defs.length === 0;
2945
+ }
2946
+ function isGlobalFetchCall(callee) {
2947
+ if (callee.type === import_utils25.AST_NODE_TYPES.Identifier) {
2948
+ return callee.name === "fetch" && resolvesToGlobal(callee);
2949
+ }
2950
+ return callee.type === import_utils25.AST_NODE_TYPES.MemberExpression && !callee.computed && callee.property.type === import_utils25.AST_NODE_TYPES.Identifier && callee.property.name === "fetch" && callee.object.type === import_utils25.AST_NODE_TYPES.Identifier && GLOBAL_OBJECTS.has(callee.object.name) && resolvesToGlobal(callee.object);
2951
+ }
2952
+ return {
2953
+ CallExpression(node) {
2954
+ if (!isGlobalFetchCall(node.callee)) {
2955
+ return;
2956
+ }
2957
+ const [first, init] = node.arguments;
2958
+ if (node.arguments.length === 1 && first !== void 0 && !isStringish(first)) {
2959
+ return;
2960
+ }
2961
+ if (init === void 0 || initProvablyLacksSignal(init)) {
2962
+ context.report({ node, messageId: "missingSignal" });
2963
+ }
2964
+ }
2965
+ };
2966
+ }
2967
+ });
2968
+
2969
+ // src/rules/require-schema-validate-search.ts
2970
+ var import_utils26 = require("@typescript-eslint/utils");
2971
+ var VALIDATOR_METHODS = /* @__PURE__ */ new Set([
2972
+ "parse",
2973
+ "safeParse",
2974
+ "decode"
2975
+ ]);
2976
+ function isConstTypeAnnotation(typeAnnotation) {
2977
+ return typeAnnotation.type === import_utils26.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === import_utils26.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
2978
+ }
2979
+ function isValidatorCall(node) {
2980
+ return node.callee.type === import_utils26.AST_NODE_TYPES.MemberExpression && !node.callee.computed && node.callee.property.type === import_utils26.AST_NODE_TYPES.Identifier && VALIDATOR_METHODS.has(node.callee.property.name);
2981
+ }
2982
+ function findCastExpression(node, insideValidatorArg) {
2983
+ if ((node.type === import_utils26.AST_NODE_TYPES.TSAsExpression || node.type === import_utils26.AST_NODE_TYPES.TSTypeAssertion) && !isConstTypeAnnotation(node.typeAnnotation) && !insideValidatorArg) {
2984
+ return node;
2985
+ }
2986
+ if (node.type === import_utils26.AST_NODE_TYPES.CallExpression && isValidatorCall(node)) {
2987
+ const inCallee = findCastExpression(node.callee, insideValidatorArg);
2988
+ if (inCallee !== null) {
2989
+ return inCallee;
2990
+ }
2991
+ for (const arg of node.arguments) {
2992
+ const found = findCastExpression(arg, true);
2993
+ if (found !== null) {
2994
+ return found;
2995
+ }
2996
+ }
2997
+ return null;
2998
+ }
2999
+ for (const key of Object.keys(node)) {
3000
+ if (key === "parent") {
3001
+ continue;
3002
+ }
3003
+ const value = node[key];
3004
+ const children = Array.isArray(value) ? value : [value];
3005
+ for (const child of children) {
3006
+ if (child !== null && typeof child === "object" && "type" in child && typeof child.type === "string") {
3007
+ const found = findCastExpression(
3008
+ child,
3009
+ insideValidatorArg
3010
+ );
3011
+ if (found !== null) {
3012
+ return found;
3013
+ }
3014
+ }
3015
+ }
3016
+ }
3017
+ return null;
3018
+ }
3019
+ var require_schema_validate_search_default = import_utils26.ESLintUtils.RuleCreator(
3020
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3021
+ )({
3022
+ name: "require-schema-validate-search",
3023
+ meta: {
3024
+ type: "problem",
3025
+ docs: {
3026
+ description: "Disallow `as` casts inside hand-rolled `validateSearch` functions; use a schema validator (e.g. zodValidator) so search params are validated at runtime."
3027
+ },
3028
+ schema: [],
3029
+ messages: {
3030
+ 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."
3031
+ }
3032
+ },
3033
+ defaultOptions: [],
3034
+ create(context) {
3035
+ if (isTestFile(context.filename)) {
3036
+ return {};
3037
+ }
3038
+ return {
3039
+ Property(node) {
3040
+ const isValidateSearchKey = !node.computed && node.key.type === import_utils26.AST_NODE_TYPES.Identifier && node.key.name === "validateSearch" || node.key.type === import_utils26.AST_NODE_TYPES.Literal && node.key.value === "validateSearch";
3041
+ if (!isValidateSearchKey) {
3042
+ return;
3043
+ }
3044
+ if (node.value.type !== import_utils26.AST_NODE_TYPES.ArrowFunctionExpression && node.value.type !== import_utils26.AST_NODE_TYPES.FunctionExpression) {
3045
+ return;
3046
+ }
3047
+ const cast = findCastExpression(node.value.body, false);
3048
+ if (cast !== null) {
3049
+ context.report({ node: cast, messageId: "castInValidateSearch" });
3050
+ }
3051
+ }
3052
+ };
3053
+ }
3054
+ });
3055
+
3056
+ // src/rules/no-fat-try-blocks.ts
3057
+ var import_utils27 = require("@typescript-eslint/utils");
2779
3058
  var MAX_TRY_BODY_STATEMENTS = 3;
2780
3059
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2781
- import_utils24.AST_NODE_TYPES.FunctionDeclaration,
2782
- import_utils24.AST_NODE_TYPES.FunctionExpression,
2783
- import_utils24.AST_NODE_TYPES.ArrowFunctionExpression
3060
+ import_utils27.AST_NODE_TYPES.FunctionDeclaration,
3061
+ import_utils27.AST_NODE_TYPES.FunctionExpression,
3062
+ import_utils27.AST_NODE_TYPES.ArrowFunctionExpression
2784
3063
  ]);
2785
3064
  var PURE_METHODS = /* @__PURE__ */ new Set([
2786
3065
  "map",
@@ -2878,20 +3157,20 @@ function isNode4(value) {
2878
3157
  }
2879
3158
  function isPureCall(node) {
2880
3159
  const callee = node.callee;
2881
- if (callee.type !== import_utils24.AST_NODE_TYPES.MemberExpression) {
3160
+ if (callee.type !== import_utils27.AST_NODE_TYPES.MemberExpression) {
2882
3161
  return false;
2883
3162
  }
2884
3163
  const property = callee.property;
2885
- if (property.type !== import_utils24.AST_NODE_TYPES.Identifier) {
3164
+ if (property.type !== import_utils27.AST_NODE_TYPES.Identifier) {
2886
3165
  return false;
2887
3166
  }
2888
- if (callee.object.type === import_utils24.AST_NODE_TYPES.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
3167
+ if (callee.object.type === import_utils27.AST_NODE_TYPES.Identifier && PURE_NAMESPACES.has(callee.object.name)) {
2889
3168
  return true;
2890
3169
  }
2891
3170
  return PURE_METHODS.has(property.name);
2892
3171
  }
2893
3172
  function isPureNew(node) {
2894
- return node.callee.type === import_utils24.AST_NODE_TYPES.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
3173
+ return node.callee.type === import_utils27.AST_NODE_TYPES.Identifier && PURE_CONSTRUCTORS.has(node.callee.name);
2895
3174
  }
2896
3175
  function subtreeMatches(stmt, predicate) {
2897
3176
  let found = false;
@@ -2928,14 +3207,14 @@ function subtreeMatches(stmt, predicate) {
2928
3207
  visit(stmt);
2929
3208
  return found;
2930
3209
  }
2931
- var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === import_utils24.AST_NODE_TYPES.AwaitExpression);
3210
+ var hasAwait = (stmt) => subtreeMatches(stmt, (n) => n.type === import_utils27.AST_NODE_TYPES.AwaitExpression);
2932
3211
  var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2933
3212
  stmt,
2934
- (n) => n.type === import_utils24.AST_NODE_TYPES.CallExpression && !isPureCall(n) || n.type === import_utils24.AST_NODE_TYPES.NewExpression && !isPureNew(n)
3213
+ (n) => n.type === import_utils27.AST_NODE_TYPES.CallExpression && !isPureCall(n) || n.type === import_utils27.AST_NODE_TYPES.NewExpression && !isPureNew(n)
2935
3214
  );
2936
3215
  function unwrap2(expr) {
2937
3216
  let current = expr;
2938
- while (current.type === import_utils24.AST_NODE_TYPES.ChainExpression || current.type === import_utils24.AST_NODE_TYPES.TSNonNullExpression) {
3217
+ while (current.type === import_utils27.AST_NODE_TYPES.ChainExpression || current.type === import_utils27.AST_NODE_TYPES.TSNonNullExpression) {
2939
3218
  current = current.expression;
2940
3219
  }
2941
3220
  return current;
@@ -2944,7 +3223,7 @@ function canThrow(stmt) {
2944
3223
  if (hasAwait(stmt)) {
2945
3224
  return true;
2946
3225
  }
2947
- if (stmt.type === import_utils24.AST_NODE_TYPES.ExpressionStatement && unwrap2(stmt.expression).type === import_utils24.AST_NODE_TYPES.CallExpression) {
3226
+ if (stmt.type === import_utils27.AST_NODE_TYPES.ExpressionStatement && unwrap2(stmt.expression).type === import_utils27.AST_NODE_TYPES.CallExpression) {
2948
3227
  return false;
2949
3228
  }
2950
3229
  return hasThrowingCallOrNew(stmt);
@@ -2955,9 +3234,9 @@ function handlerRethrows(handler) {
2955
3234
  }
2956
3235
  const body = handler.body.body;
2957
3236
  const last = body[body.length - 1];
2958
- return last !== void 0 && last.type === import_utils24.AST_NODE_TYPES.ThrowStatement;
3237
+ return last !== void 0 && last.type === import_utils27.AST_NODE_TYPES.ThrowStatement;
2959
3238
  }
2960
- var no_fat_try_blocks_default = import_utils24.ESLintUtils.RuleCreator(
3239
+ var no_fat_try_blocks_default = import_utils27.ESLintUtils.RuleCreator(
2961
3240
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2962
3241
  )({
2963
3242
  name: "no-fat-try-blocks",
@@ -2998,7 +3277,7 @@ var no_fat_try_blocks_default = import_utils24.ESLintUtils.RuleCreator(
2998
3277
  });
2999
3278
 
3000
3279
  // src/rules/no-secret-in-log.ts
3001
- var import_utils25 = require("@typescript-eslint/utils");
3280
+ var import_utils28 = require("@typescript-eslint/utils");
3002
3281
  var LOG_METHODS2 = /* @__PURE__ */ new Set([
3003
3282
  "debug",
3004
3283
  "info",
@@ -3196,7 +3475,7 @@ function propertyKeyName2(prop) {
3196
3475
  }
3197
3476
  return null;
3198
3477
  }
3199
- var no_secret_in_log_default = import_utils25.ESLintUtils.RuleCreator(
3478
+ var no_secret_in_log_default = import_utils28.ESLintUtils.RuleCreator(
3200
3479
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3201
3480
  )({
3202
3481
  name: "no-secret-in-log",
@@ -3264,15 +3543,15 @@ var no_secret_in_log_default = import_utils25.ESLintUtils.RuleCreator(
3264
3543
  });
3265
3544
 
3266
3545
  // src/rules/no-unsafe-cast.ts
3267
- var import_utils26 = require("@typescript-eslint/utils");
3268
- var import_utils27 = require("@typescript-eslint/utils");
3546
+ var import_utils29 = require("@typescript-eslint/utils");
3547
+ var import_utils30 = require("@typescript-eslint/utils");
3269
3548
  function isAnyAnnotation(node) {
3270
- return node.type === import_utils27.AST_NODE_TYPES.TSAnyKeyword;
3549
+ return node.type === import_utils30.AST_NODE_TYPES.TSAnyKeyword;
3271
3550
  }
3272
3551
  function isConstAssertion(typeAnnotation) {
3273
- return typeAnnotation.type === import_utils27.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === import_utils27.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
3552
+ return typeAnnotation.type === import_utils30.AST_NODE_TYPES.TSTypeReference && typeAnnotation.typeName.type === import_utils30.AST_NODE_TYPES.Identifier && typeAnnotation.typeName.name === "const";
3274
3553
  }
3275
- var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3554
+ var no_unsafe_cast_default = import_utils29.ESLintUtils.RuleCreator(
3276
3555
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3277
3556
  )({
3278
3557
  name: "no-unsafe-cast",
@@ -3298,7 +3577,7 @@ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3298
3577
  return;
3299
3578
  }
3300
3579
  const inner = node.expression;
3301
- if (inner.type === import_utils27.AST_NODE_TYPES.TSAsExpression || inner.type === import_utils27.AST_NODE_TYPES.TSTypeAssertion) {
3580
+ if (inner.type === import_utils30.AST_NODE_TYPES.TSAsExpression || inner.type === import_utils30.AST_NODE_TYPES.TSTypeAssertion) {
3302
3581
  context.report({ node, messageId: "doubleCast" });
3303
3582
  }
3304
3583
  }
@@ -3310,7 +3589,7 @@ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3310
3589
  });
3311
3590
 
3312
3591
  // src/rules/prefer-string-literal-union.ts
3313
- var import_utils28 = require("@typescript-eslint/utils");
3592
+ var import_utils31 = require("@typescript-eslint/utils");
3314
3593
  var ts = __toESM(require("typescript"), 1);
3315
3594
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
3316
3595
  "status",
@@ -3353,19 +3632,19 @@ function isChoiceLikeName(name) {
3353
3632
  return CHOICE_TOKENS.has(lastWord(name));
3354
3633
  }
3355
3634
  function keyName(key) {
3356
- if (key.type === import_utils28.AST_NODE_TYPES.Identifier) {
3635
+ if (key.type === import_utils31.AST_NODE_TYPES.Identifier) {
3357
3636
  return key.name;
3358
3637
  }
3359
- if (key.type === import_utils28.AST_NODE_TYPES.Literal && typeof key.value === "string") {
3638
+ if (key.type === import_utils31.AST_NODE_TYPES.Literal && typeof key.value === "string") {
3360
3639
  return key.value;
3361
3640
  }
3362
3641
  return null;
3363
3642
  }
3364
3643
  function isStringLiteralMember(t) {
3365
- return t.type === import_utils28.AST_NODE_TYPES.TSLiteralType && t.literal.type === import_utils28.AST_NODE_TYPES.Literal && typeof t.literal.value === "string";
3644
+ return t.type === import_utils31.AST_NODE_TYPES.TSLiteralType && t.literal.type === import_utils31.AST_NODE_TYPES.Literal && typeof t.literal.value === "string";
3366
3645
  }
3367
3646
  function isStringLiteralUnion(node) {
3368
- if (node?.type !== import_utils28.AST_NODE_TYPES.TSUnionType) {
3647
+ if (node?.type !== import_utils31.AST_NODE_TYPES.TSUnionType) {
3369
3648
  return false;
3370
3649
  }
3371
3650
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -3394,12 +3673,12 @@ function bindingSourceExpression(decl) {
3394
3673
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
3395
3674
  }
3396
3675
  function refKey(node) {
3397
- if (node.type === import_utils28.AST_NODE_TYPES.Identifier) {
3676
+ if (node.type === import_utils31.AST_NODE_TYPES.Identifier) {
3398
3677
  return node.name;
3399
3678
  }
3400
- if (node.type === import_utils28.AST_NODE_TYPES.MemberExpression && !node.computed) {
3679
+ if (node.type === import_utils31.AST_NODE_TYPES.MemberExpression && !node.computed) {
3401
3680
  const inner = refKey(node.object);
3402
- if (inner === null || node.property.type !== import_utils28.AST_NODE_TYPES.Identifier) {
3681
+ if (inner === null || node.property.type !== import_utils31.AST_NODE_TYPES.Identifier) {
3403
3682
  return null;
3404
3683
  }
3405
3684
  return `${inner}.${node.property.name}`;
@@ -3407,12 +3686,12 @@ function refKey(node) {
3407
3686
  return null;
3408
3687
  }
3409
3688
  function strLiteral(node) {
3410
- if (node.type === import_utils28.AST_NODE_TYPES.Literal && typeof node.value === "string") {
3689
+ if (node.type === import_utils31.AST_NODE_TYPES.Literal && typeof node.value === "string") {
3411
3690
  return node.value;
3412
3691
  }
3413
3692
  return null;
3414
3693
  }
3415
- var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator(
3694
+ var prefer_string_literal_union_default = import_utils31.ESLintUtils.RuleCreator(
3416
3695
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3417
3696
  )({
3418
3697
  name: "prefer-string-literal-union",
@@ -3436,7 +3715,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3436
3715
  }
3437
3716
  let services;
3438
3717
  try {
3439
- services = import_utils28.ESLintUtils.getParserServices(context);
3718
+ services = import_utils31.ESLintUtils.getParserServices(context);
3440
3719
  } catch {
3441
3720
  services = null;
3442
3721
  }
@@ -3520,7 +3799,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3520
3799
  containersWithUnion.add(container);
3521
3800
  return;
3522
3801
  }
3523
- if (typeNode?.type !== import_utils28.AST_NODE_TYPES.TSStringKeyword) {
3802
+ if (typeNode?.type !== import_utils31.AST_NODE_TYPES.TSStringKeyword) {
3524
3803
  return;
3525
3804
  }
3526
3805
  const name = keyName(key);
@@ -3608,10 +3887,10 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3608
3887
  }
3609
3888
  };
3610
3889
  function refKeyText(node) {
3611
- if (node.type === import_utils28.AST_NODE_TYPES.BinaryExpression) {
3890
+ if (node.type === import_utils31.AST_NODE_TYPES.BinaryExpression) {
3612
3891
  return refKey(node.left) ?? refKey(node.right) ?? "value";
3613
3892
  }
3614
- if (node.type === import_utils28.AST_NODE_TYPES.SwitchStatement) {
3893
+ if (node.type === import_utils31.AST_NODE_TYPES.SwitchStatement) {
3615
3894
  return refKey(node.discriminant) ?? "value";
3616
3895
  }
3617
3896
  return "value";
@@ -3620,7 +3899,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3620
3899
  });
3621
3900
 
3622
3901
  // src/rules/single-public-export.ts
3623
- var import_utils29 = require("@typescript-eslint/utils");
3902
+ var import_utils32 = require("@typescript-eslint/utils");
3624
3903
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
3625
3904
  "util",
3626
3905
  "utils",
@@ -3643,7 +3922,7 @@ var ACRONYM_OVERRIDES = [
3643
3922
  [/gRPC/g, "Grpc"]
3644
3923
  ];
3645
3924
  var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
3646
- var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3925
+ var TEST_FILE_RE2 = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3647
3926
  var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
3648
3927
  var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
3649
3928
  var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
@@ -3654,12 +3933,12 @@ var kebabCase2 = (name) => {
3654
3933
  }
3655
3934
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
3656
3935
  };
3657
- var isFunctionExpression = (node) => node !== null && (node.type === import_utils29.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils29.AST_NODE_TYPES.FunctionExpression);
3936
+ var isFunctionExpression = (node) => node !== null && (node.type === import_utils32.AST_NODE_TYPES.ArrowFunctionExpression || node.type === import_utils32.AST_NODE_TYPES.FunctionExpression);
3658
3937
  var functionConstName = (decl) => {
3659
3938
  if (decl.declarations.length !== 1) return null;
3660
3939
  const [declarator] = decl.declarations;
3661
3940
  if (declarator === void 0) return null;
3662
- if (declarator.id.type !== import_utils29.AST_NODE_TYPES.Identifier) return null;
3941
+ if (declarator.id.type !== import_utils32.AST_NODE_TYPES.Identifier) return null;
3663
3942
  if (!isFunctionExpression(declarator.init)) return null;
3664
3943
  return declarator.id.name;
3665
3944
  };
@@ -3673,20 +3952,20 @@ var summarizeExports = (body) => {
3673
3952
  };
3674
3953
  for (const statement of body) {
3675
3954
  switch (statement.type) {
3676
- case import_utils29.AST_NODE_TYPES.ExportAllDeclaration:
3955
+ case import_utils32.AST_NODE_TYPES.ExportAllDeclaration:
3677
3956
  hasReExport = true;
3678
3957
  break;
3679
- case import_utils29.AST_NODE_TYPES.ExportDefaultDeclaration: {
3958
+ case import_utils32.AST_NODE_TYPES.ExportDefaultDeclaration: {
3680
3959
  names += 1;
3681
3960
  const decl = statement.declaration;
3682
- if (decl.type === import_utils29.AST_NODE_TYPES.FunctionDeclaration && decl.id !== null) {
3961
+ if (decl.type === import_utils32.AST_NODE_TYPES.FunctionDeclaration && decl.id !== null) {
3683
3962
  candidate = { name: decl.id.name, node: statement };
3684
- } else if (decl.type === import_utils29.AST_NODE_TYPES.ClassDeclaration && decl.id !== null) {
3963
+ } else if (decl.type === import_utils32.AST_NODE_TYPES.ClassDeclaration && decl.id !== null) {
3685
3964
  candidate = { name: decl.id.name, node: statement };
3686
3965
  }
3687
3966
  break;
3688
3967
  }
3689
- case import_utils29.AST_NODE_TYPES.ExportNamedDeclaration: {
3968
+ case import_utils32.AST_NODE_TYPES.ExportNamedDeclaration: {
3690
3969
  if (statement.source !== null) {
3691
3970
  hasReExport = true;
3692
3971
  break;
@@ -3697,15 +3976,15 @@ var summarizeExports = (body) => {
3697
3976
  break;
3698
3977
  }
3699
3978
  switch (decl.type) {
3700
- case import_utils29.AST_NODE_TYPES.FunctionDeclaration:
3979
+ case import_utils32.AST_NODE_TYPES.FunctionDeclaration:
3701
3980
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3702
3981
  else names += 1;
3703
3982
  break;
3704
- case import_utils29.AST_NODE_TYPES.ClassDeclaration:
3983
+ case import_utils32.AST_NODE_TYPES.ClassDeclaration:
3705
3984
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3706
3985
  else names += 1;
3707
3986
  break;
3708
- case import_utils29.AST_NODE_TYPES.VariableDeclaration: {
3987
+ case import_utils32.AST_NODE_TYPES.VariableDeclaration: {
3709
3988
  const fnName = functionConstName(decl);
3710
3989
  if (fnName !== null && decl.declarations.length === 1) {
3711
3990
  addCandidate(fnName, statement);
@@ -3725,7 +4004,7 @@ var summarizeExports = (body) => {
3725
4004
  }
3726
4005
  return { names, hasReExport, candidate };
3727
4006
  };
3728
- var single_public_export_default = import_utils29.ESLintUtils.RuleCreator(
4007
+ var single_public_export_default = import_utils32.ESLintUtils.RuleCreator(
3729
4008
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3730
4009
  )({
3731
4010
  name: "single-public-export",
@@ -3743,7 +4022,7 @@ var single_public_export_default = import_utils29.ESLintUtils.RuleCreator(
3743
4022
  create(context) {
3744
4023
  const base = basename(context.filename);
3745
4024
  if (base.endsWith(".d.ts")) return {};
3746
- if (TEST_FILE_RE.test(base)) return {};
4025
+ if (TEST_FILE_RE2.test(base)) return {};
3747
4026
  const stem = stemOf(base);
3748
4027
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
3749
4028
  return {
@@ -3791,12 +4070,15 @@ var rules = {
3791
4070
  "no-secret-in-log": no_secret_in_log_default,
3792
4071
  "no-unsafe-cast": no_unsafe_cast_default,
3793
4072
  "prefer-string-literal-union": prefer_string_literal_union_default,
3794
- "single-public-export": single_public_export_default
4073
+ "single-public-export": single_public_export_default,
4074
+ "no-silent-promise-catch": no_silent_promise_catch_default,
4075
+ "require-fetch-timeout": require_fetch_timeout_default,
4076
+ "require-schema-validate-search": require_schema_validate_search_default
3795
4077
  };
3796
4078
  var plugin = {
3797
4079
  meta: {
3798
4080
  name: "@sarj/eslint-plugin",
3799
- version: "2.6.0"
4081
+ version: "2.7.0"
3800
4082
  },
3801
4083
  rules,
3802
4084
  configs: {
@@ -3828,7 +4110,11 @@ var plugin = {
3828
4110
  "@sarj/no-secret-in-log": "warn",
3829
4111
  "@sarj/no-unsafe-cast": "warn",
3830
4112
  "@sarj/single-public-export": "warn",
3831
- "@sarj/prefer-string-literal-union": "warn"
4113
+ "@sarj/prefer-string-literal-union": "warn",
4114
+ // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
4115
+ "@sarj/require-fetch-timeout": "warn",
4116
+ "@sarj/no-silent-promise-catch": "warn",
4117
+ "@sarj/require-schema-validate-search": "warn"
3832
4118
  }
3833
4119
  },
3834
4120
  strict: {
@@ -3864,7 +4150,11 @@ var plugin = {
3864
4150
  "@sarj/no-unsafe-cast": "warn",
3865
4151
  "@sarj/single-public-export": "error",
3866
4152
  // High-volume/stylistic — warn until rollout proves FP rate.
3867
- "@sarj/prefer-string-literal-union": "warn"
4153
+ "@sarj/prefer-string-literal-union": "warn",
4154
+ // Mined from 2y of PR review feedback + 5-repo code-smell audit (2026-07).
4155
+ "@sarj/require-fetch-timeout": "error",
4156
+ "@sarj/no-silent-promise-catch": "error",
4157
+ "@sarj/require-schema-validate-search": "error"
3868
4158
  }
3869
4159
  }
3870
4160
  }