@sarj/eslint-plugin 2.5.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
@@ -213,6 +213,8 @@ var no_client_side_data_fetching_default = ESLintUtils2.RuleCreator(
213
213
  // src/rules/no-comment-cruft.ts
214
214
  import { ESLintUtils as ESLintUtils3 } from "@typescript-eslint/utils";
215
215
  var LEADING_PREAMBLE_MIN = 4;
216
+ var STEP_NARRATION_RE = /^(?:first(?:ly)?|second(?:ly)?|third(?:ly)?|then|next|after(?:wards| that)?|finally|lastly|now)\s*[,:]\s*\S|^step\s+\d+\b/i;
217
+ var META_COMMENTARY_RE = /\b(?:for now|keeping (?:it|this) simple|could be (?:refactored|improved|cleaned up|simplified)|refactor(?:ed|ing)? (?:later|this)|not sure (?:if|whether|why|how)|quick[- ](?:and[- ]dirty|fix)|(?:a |bit of a )?hacky|is a hack|temporary (?:solution|workaround|fix|hack)|revisit (?:this|later|below)|clean (?:this|it) up|not ideal|placeholder for now)\b/i;
216
218
  var DIRECTIVE_RE = /^(eslint\b|eslint-|@ts-|prettier-ignore|prettier\b|biome-|c8\b|v8\b|istanbul\b|@type\b|@vite|webpack|<reference|<amd|global\b|noinspection|todo\b|fixme\b|hack\b|xxx\b)/i;
217
219
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
218
220
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
@@ -251,6 +253,13 @@ function isProse(text) {
251
253
  }
252
254
  return false;
253
255
  }
256
+ function isRedundantNarration(body) {
257
+ const t = body.trim();
258
+ if (!t || looksLikeCode(t) || hasPseudocode(t)) return false;
259
+ if (STEP_NARRATION_RE.test(t)) return true;
260
+ if (META_COMMENTARY_RE.test(t)) return true;
261
+ return false;
262
+ }
254
263
  function hasCommentedOutCode(texts, precedingProse) {
255
264
  for (let i = 0; i < texts.length; i++) {
256
265
  const line = texts[i];
@@ -276,7 +285,8 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
276
285
  messages: {
277
286
  commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
278
287
  sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
279
- fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
288
+ fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
289
+ redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting."
280
290
  }
281
291
  },
282
292
  defaultOptions: [],
@@ -331,6 +341,13 @@ var no_comment_cruft_default = ESLintUtils3.RuleCreator(
331
341
  const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
332
342
  if (hasCommentedOutCode(texts, precedingProse)) {
333
343
  context.report({ node: comment, messageId: "commentedOutCode" });
344
+ continue;
345
+ }
346
+ if (comment.type === "Line" && texts.length === 1) {
347
+ const body = texts[0];
348
+ if (body !== void 0 && isRedundantNarration(body)) {
349
+ context.report({ node: comment, messageId: "redundantNarration" });
350
+ }
334
351
  }
335
352
  }
336
353
  reportLeadingPreamble(comments, firstCodeLine);
@@ -2738,16 +2755,299 @@ var no_cors_wildcard_with_credentials_default = ESLintUtils21.RuleCreator(
2738
2755
  }
2739
2756
  });
2740
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
+
2741
3041
  // src/rules/no-fat-try-blocks.ts
2742
3042
  import {
2743
- ESLintUtils as ESLintUtils22,
2744
- AST_NODE_TYPES as AST_NODE_TYPES12
3043
+ ESLintUtils as ESLintUtils25,
3044
+ AST_NODE_TYPES as AST_NODE_TYPES15
2745
3045
  } from "@typescript-eslint/utils";
2746
3046
  var MAX_TRY_BODY_STATEMENTS = 3;
2747
3047
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2748
- AST_NODE_TYPES12.FunctionDeclaration,
2749
- AST_NODE_TYPES12.FunctionExpression,
2750
- AST_NODE_TYPES12.ArrowFunctionExpression
3048
+ AST_NODE_TYPES15.FunctionDeclaration,
3049
+ AST_NODE_TYPES15.FunctionExpression,
3050
+ AST_NODE_TYPES15.ArrowFunctionExpression
2751
3051
  ]);
2752
3052
  var PURE_METHODS = /* @__PURE__ */ new Set([
2753
3053
  "map",
@@ -2845,20 +3145,20 @@ function isNode4(value) {
2845
3145
  }
2846
3146
  function isPureCall(node) {
2847
3147
  const callee = node.callee;
2848
- if (callee.type !== AST_NODE_TYPES12.MemberExpression) {
3148
+ if (callee.type !== AST_NODE_TYPES15.MemberExpression) {
2849
3149
  return false;
2850
3150
  }
2851
3151
  const property = callee.property;
2852
- if (property.type !== AST_NODE_TYPES12.Identifier) {
3152
+ if (property.type !== AST_NODE_TYPES15.Identifier) {
2853
3153
  return false;
2854
3154
  }
2855
- 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)) {
2856
3156
  return true;
2857
3157
  }
2858
3158
  return PURE_METHODS.has(property.name);
2859
3159
  }
2860
3160
  function isPureNew(node) {
2861
- 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);
2862
3162
  }
2863
3163
  function subtreeMatches(stmt, predicate) {
2864
3164
  let found = false;
@@ -2895,14 +3195,14 @@ function subtreeMatches(stmt, predicate) {
2895
3195
  visit(stmt);
2896
3196
  return found;
2897
3197
  }
2898
- 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);
2899
3199
  var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2900
3200
  stmt,
2901
- (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)
2902
3202
  );
2903
3203
  function unwrap2(expr) {
2904
3204
  let current = expr;
2905
- 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) {
2906
3206
  current = current.expression;
2907
3207
  }
2908
3208
  return current;
@@ -2911,7 +3211,7 @@ function canThrow(stmt) {
2911
3211
  if (hasAwait(stmt)) {
2912
3212
  return true;
2913
3213
  }
2914
- 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) {
2915
3215
  return false;
2916
3216
  }
2917
3217
  return hasThrowingCallOrNew(stmt);
@@ -2922,9 +3222,9 @@ function handlerRethrows(handler) {
2922
3222
  }
2923
3223
  const body = handler.body.body;
2924
3224
  const last = body[body.length - 1];
2925
- return last !== void 0 && last.type === AST_NODE_TYPES12.ThrowStatement;
3225
+ return last !== void 0 && last.type === AST_NODE_TYPES15.ThrowStatement;
2926
3226
  }
2927
- var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
3227
+ var no_fat_try_blocks_default = ESLintUtils25.RuleCreator(
2928
3228
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2929
3229
  )({
2930
3230
  name: "no-fat-try-blocks",
@@ -2965,7 +3265,7 @@ var no_fat_try_blocks_default = ESLintUtils22.RuleCreator(
2965
3265
  });
2966
3266
 
2967
3267
  // src/rules/no-secret-in-log.ts
2968
- import { ESLintUtils as ESLintUtils23 } from "@typescript-eslint/utils";
3268
+ import { ESLintUtils as ESLintUtils26 } from "@typescript-eslint/utils";
2969
3269
  var LOG_METHODS2 = /* @__PURE__ */ new Set([
2970
3270
  "debug",
2971
3271
  "info",
@@ -3163,7 +3463,7 @@ function propertyKeyName2(prop) {
3163
3463
  }
3164
3464
  return null;
3165
3465
  }
3166
- var no_secret_in_log_default = ESLintUtils23.RuleCreator(
3466
+ var no_secret_in_log_default = ESLintUtils26.RuleCreator(
3167
3467
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3168
3468
  )({
3169
3469
  name: "no-secret-in-log",
@@ -3231,15 +3531,15 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
3231
3531
  });
3232
3532
 
3233
3533
  // src/rules/no-unsafe-cast.ts
3234
- import { ESLintUtils as ESLintUtils24 } from "@typescript-eslint/utils";
3235
- 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";
3236
3536
  function isAnyAnnotation(node) {
3237
- return node.type === AST_NODE_TYPES13.TSAnyKeyword;
3537
+ return node.type === AST_NODE_TYPES16.TSAnyKeyword;
3238
3538
  }
3239
3539
  function isConstAssertion(typeAnnotation) {
3240
- 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";
3241
3541
  }
3242
- var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3542
+ var no_unsafe_cast_default = ESLintUtils27.RuleCreator(
3243
3543
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3244
3544
  )({
3245
3545
  name: "no-unsafe-cast",
@@ -3265,7 +3565,7 @@ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3265
3565
  return;
3266
3566
  }
3267
3567
  const inner = node.expression;
3268
- 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) {
3269
3569
  context.report({ node, messageId: "doubleCast" });
3270
3570
  }
3271
3571
  }
@@ -3278,8 +3578,8 @@ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3278
3578
 
3279
3579
  // src/rules/prefer-string-literal-union.ts
3280
3580
  import {
3281
- ESLintUtils as ESLintUtils25,
3282
- AST_NODE_TYPES as AST_NODE_TYPES14
3581
+ ESLintUtils as ESLintUtils28,
3582
+ AST_NODE_TYPES as AST_NODE_TYPES17
3283
3583
  } from "@typescript-eslint/utils";
3284
3584
  import * as ts from "typescript";
3285
3585
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
@@ -3323,19 +3623,19 @@ function isChoiceLikeName(name) {
3323
3623
  return CHOICE_TOKENS.has(lastWord(name));
3324
3624
  }
3325
3625
  function keyName(key) {
3326
- if (key.type === AST_NODE_TYPES14.Identifier) {
3626
+ if (key.type === AST_NODE_TYPES17.Identifier) {
3327
3627
  return key.name;
3328
3628
  }
3329
- if (key.type === AST_NODE_TYPES14.Literal && typeof key.value === "string") {
3629
+ if (key.type === AST_NODE_TYPES17.Literal && typeof key.value === "string") {
3330
3630
  return key.value;
3331
3631
  }
3332
3632
  return null;
3333
3633
  }
3334
3634
  function isStringLiteralMember(t) {
3335
- 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";
3336
3636
  }
3337
3637
  function isStringLiteralUnion(node) {
3338
- if (node?.type !== AST_NODE_TYPES14.TSUnionType) {
3638
+ if (node?.type !== AST_NODE_TYPES17.TSUnionType) {
3339
3639
  return false;
3340
3640
  }
3341
3641
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -3364,12 +3664,12 @@ function bindingSourceExpression(decl) {
3364
3664
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
3365
3665
  }
3366
3666
  function refKey(node) {
3367
- if (node.type === AST_NODE_TYPES14.Identifier) {
3667
+ if (node.type === AST_NODE_TYPES17.Identifier) {
3368
3668
  return node.name;
3369
3669
  }
3370
- if (node.type === AST_NODE_TYPES14.MemberExpression && !node.computed) {
3670
+ if (node.type === AST_NODE_TYPES17.MemberExpression && !node.computed) {
3371
3671
  const inner = refKey(node.object);
3372
- if (inner === null || node.property.type !== AST_NODE_TYPES14.Identifier) {
3672
+ if (inner === null || node.property.type !== AST_NODE_TYPES17.Identifier) {
3373
3673
  return null;
3374
3674
  }
3375
3675
  return `${inner}.${node.property.name}`;
@@ -3377,12 +3677,12 @@ function refKey(node) {
3377
3677
  return null;
3378
3678
  }
3379
3679
  function strLiteral(node) {
3380
- if (node.type === AST_NODE_TYPES14.Literal && typeof node.value === "string") {
3680
+ if (node.type === AST_NODE_TYPES17.Literal && typeof node.value === "string") {
3381
3681
  return node.value;
3382
3682
  }
3383
3683
  return null;
3384
3684
  }
3385
- var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3685
+ var prefer_string_literal_union_default = ESLintUtils28.RuleCreator(
3386
3686
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3387
3687
  )({
3388
3688
  name: "prefer-string-literal-union",
@@ -3406,7 +3706,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3406
3706
  }
3407
3707
  let services;
3408
3708
  try {
3409
- services = ESLintUtils25.getParserServices(context);
3709
+ services = ESLintUtils28.getParserServices(context);
3410
3710
  } catch {
3411
3711
  services = null;
3412
3712
  }
@@ -3490,7 +3790,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3490
3790
  containersWithUnion.add(container);
3491
3791
  return;
3492
3792
  }
3493
- if (typeNode?.type !== AST_NODE_TYPES14.TSStringKeyword) {
3793
+ if (typeNode?.type !== AST_NODE_TYPES17.TSStringKeyword) {
3494
3794
  return;
3495
3795
  }
3496
3796
  const name = keyName(key);
@@ -3578,10 +3878,10 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3578
3878
  }
3579
3879
  };
3580
3880
  function refKeyText(node) {
3581
- if (node.type === AST_NODE_TYPES14.BinaryExpression) {
3881
+ if (node.type === AST_NODE_TYPES17.BinaryExpression) {
3582
3882
  return refKey(node.left) ?? refKey(node.right) ?? "value";
3583
3883
  }
3584
- if (node.type === AST_NODE_TYPES14.SwitchStatement) {
3884
+ if (node.type === AST_NODE_TYPES17.SwitchStatement) {
3585
3885
  return refKey(node.discriminant) ?? "value";
3586
3886
  }
3587
3887
  return "value";
@@ -3590,7 +3890,7 @@ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3590
3890
  });
3591
3891
 
3592
3892
  // src/rules/single-public-export.ts
3593
- 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";
3594
3894
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
3595
3895
  "util",
3596
3896
  "utils",
@@ -3613,7 +3913,7 @@ var ACRONYM_OVERRIDES = [
3613
3913
  [/gRPC/g, "Grpc"]
3614
3914
  ];
3615
3915
  var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
3616
- var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3916
+ var TEST_FILE_RE2 = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3617
3917
  var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
3618
3918
  var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
3619
3919
  var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
@@ -3624,12 +3924,12 @@ var kebabCase2 = (name) => {
3624
3924
  }
3625
3925
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
3626
3926
  };
3627
- 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);
3628
3928
  var functionConstName = (decl) => {
3629
3929
  if (decl.declarations.length !== 1) return null;
3630
3930
  const [declarator] = decl.declarations;
3631
3931
  if (declarator === void 0) return null;
3632
- if (declarator.id.type !== AST_NODE_TYPES15.Identifier) return null;
3932
+ if (declarator.id.type !== AST_NODE_TYPES18.Identifier) return null;
3633
3933
  if (!isFunctionExpression(declarator.init)) return null;
3634
3934
  return declarator.id.name;
3635
3935
  };
@@ -3643,20 +3943,20 @@ var summarizeExports = (body) => {
3643
3943
  };
3644
3944
  for (const statement of body) {
3645
3945
  switch (statement.type) {
3646
- case AST_NODE_TYPES15.ExportAllDeclaration:
3946
+ case AST_NODE_TYPES18.ExportAllDeclaration:
3647
3947
  hasReExport = true;
3648
3948
  break;
3649
- case AST_NODE_TYPES15.ExportDefaultDeclaration: {
3949
+ case AST_NODE_TYPES18.ExportDefaultDeclaration: {
3650
3950
  names += 1;
3651
3951
  const decl = statement.declaration;
3652
- if (decl.type === AST_NODE_TYPES15.FunctionDeclaration && decl.id !== null) {
3952
+ if (decl.type === AST_NODE_TYPES18.FunctionDeclaration && decl.id !== null) {
3653
3953
  candidate = { name: decl.id.name, node: statement };
3654
- } else if (decl.type === AST_NODE_TYPES15.ClassDeclaration && decl.id !== null) {
3954
+ } else if (decl.type === AST_NODE_TYPES18.ClassDeclaration && decl.id !== null) {
3655
3955
  candidate = { name: decl.id.name, node: statement };
3656
3956
  }
3657
3957
  break;
3658
3958
  }
3659
- case AST_NODE_TYPES15.ExportNamedDeclaration: {
3959
+ case AST_NODE_TYPES18.ExportNamedDeclaration: {
3660
3960
  if (statement.source !== null) {
3661
3961
  hasReExport = true;
3662
3962
  break;
@@ -3667,15 +3967,15 @@ var summarizeExports = (body) => {
3667
3967
  break;
3668
3968
  }
3669
3969
  switch (decl.type) {
3670
- case AST_NODE_TYPES15.FunctionDeclaration:
3970
+ case AST_NODE_TYPES18.FunctionDeclaration:
3671
3971
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3672
3972
  else names += 1;
3673
3973
  break;
3674
- case AST_NODE_TYPES15.ClassDeclaration:
3974
+ case AST_NODE_TYPES18.ClassDeclaration:
3675
3975
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3676
3976
  else names += 1;
3677
3977
  break;
3678
- case AST_NODE_TYPES15.VariableDeclaration: {
3978
+ case AST_NODE_TYPES18.VariableDeclaration: {
3679
3979
  const fnName = functionConstName(decl);
3680
3980
  if (fnName !== null && decl.declarations.length === 1) {
3681
3981
  addCandidate(fnName, statement);
@@ -3695,7 +3995,7 @@ var summarizeExports = (body) => {
3695
3995
  }
3696
3996
  return { names, hasReExport, candidate };
3697
3997
  };
3698
- var single_public_export_default = ESLintUtils26.RuleCreator(
3998
+ var single_public_export_default = ESLintUtils29.RuleCreator(
3699
3999
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3700
4000
  )({
3701
4001
  name: "single-public-export",
@@ -3713,7 +4013,7 @@ var single_public_export_default = ESLintUtils26.RuleCreator(
3713
4013
  create(context) {
3714
4014
  const base = basename(context.filename);
3715
4015
  if (base.endsWith(".d.ts")) return {};
3716
- if (TEST_FILE_RE.test(base)) return {};
4016
+ if (TEST_FILE_RE2.test(base)) return {};
3717
4017
  const stem = stemOf(base);
3718
4018
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
3719
4019
  return {
@@ -3761,12 +4061,15 @@ var rules = {
3761
4061
  "no-secret-in-log": no_secret_in_log_default,
3762
4062
  "no-unsafe-cast": no_unsafe_cast_default,
3763
4063
  "prefer-string-literal-union": prefer_string_literal_union_default,
3764
- "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
3765
4068
  };
3766
4069
  var plugin = {
3767
4070
  meta: {
3768
4071
  name: "@sarj/eslint-plugin",
3769
- version: "2.5.0"
4072
+ version: "2.7.0"
3770
4073
  },
3771
4074
  rules,
3772
4075
  configs: {
@@ -3798,7 +4101,11 @@ var plugin = {
3798
4101
  "@sarj/no-secret-in-log": "warn",
3799
4102
  "@sarj/no-unsafe-cast": "warn",
3800
4103
  "@sarj/single-public-export": "warn",
3801
- "@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"
3802
4109
  }
3803
4110
  },
3804
4111
  strict: {
@@ -3834,7 +4141,11 @@ var plugin = {
3834
4141
  "@sarj/no-unsafe-cast": "warn",
3835
4142
  "@sarj/single-public-export": "error",
3836
4143
  // High-volume/stylistic — warn until rollout proves FP rate.
3837
- "@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"
3838
4149
  }
3839
4150
  }
3840
4151
  }