@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.cjs CHANGED
@@ -247,6 +247,8 @@ var no_client_side_data_fetching_default = import_utils2.ESLintUtils.RuleCreator
247
247
  // src/rules/no-comment-cruft.ts
248
248
  var import_utils3 = require("@typescript-eslint/utils");
249
249
  var LEADING_PREAMBLE_MIN = 4;
250
+ 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;
251
+ 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;
250
252
  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;
251
253
  var LICENSE_RE = /copyright|licen[cs]ed?|spdx|permission is hereby granted|all rights reserved/i;
252
254
  var BANNER_FULL_RE = /^[\s\-=*#~_+.]{4,}$/;
@@ -285,6 +287,13 @@ function isProse(text) {
285
287
  }
286
288
  return false;
287
289
  }
290
+ function isRedundantNarration(body) {
291
+ const t = body.trim();
292
+ if (!t || looksLikeCode(t) || hasPseudocode(t)) return false;
293
+ if (STEP_NARRATION_RE.test(t)) return true;
294
+ if (META_COMMENTARY_RE.test(t)) return true;
295
+ return false;
296
+ }
288
297
  function hasCommentedOutCode(texts, precedingProse) {
289
298
  for (let i = 0; i < texts.length; i++) {
290
299
  const line = texts[i];
@@ -310,7 +319,8 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
310
319
  messages: {
311
320
  commentedOutCode: "Commented-out code \u2014 delete it; git history remembers.",
312
321
  sectionBanner: "Section-banner / region comment \u2014 structure code with functions, not ASCII rules.",
313
- fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines."
322
+ fileHeaderPreamble: "File-header comment preamble \u2014 use a brief doc comment for the why, not a block of `//` lines.",
323
+ redundantNarration: "Comment narrates the code \u2014 delete it or say *why*, not *what*. Code is self-documenting."
314
324
  }
315
325
  },
316
326
  defaultOptions: [],
@@ -365,6 +375,13 @@ var no_comment_cruft_default = import_utils3.ESLintUtils.RuleCreator(
365
375
  const precedingProse = prev !== void 0 && prev.type === "Line" && prev.loc.end.line === comment.loc.start.line - 1 && isProse(stripCommentMarker(prev.value));
366
376
  if (hasCommentedOutCode(texts, precedingProse)) {
367
377
  context.report({ node: comment, messageId: "commentedOutCode" });
378
+ continue;
379
+ }
380
+ if (comment.type === "Line" && texts.length === 1) {
381
+ const body = texts[0];
382
+ if (body !== void 0 && isRedundantNarration(body)) {
383
+ context.report({ node: comment, messageId: "redundantNarration" });
384
+ }
368
385
  }
369
386
  }
370
387
  reportLeadingPreamble(comments, firstCodeLine);
@@ -2757,13 +2774,292 @@ var no_cors_wildcard_with_credentials_default = import_utils23.ESLintUtils.RuleC
2757
2774
  }
2758
2775
  });
2759
2776
 
2760
- // src/rules/no-fat-try-blocks.ts
2777
+ // src/rules/no-silent-promise-catch.ts
2761
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");
2762
3058
  var MAX_TRY_BODY_STATEMENTS = 3;
2763
3059
  var NESTED_FUNCTION_TYPES = /* @__PURE__ */ new Set([
2764
- import_utils24.AST_NODE_TYPES.FunctionDeclaration,
2765
- import_utils24.AST_NODE_TYPES.FunctionExpression,
2766
- 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
2767
3063
  ]);
2768
3064
  var PURE_METHODS = /* @__PURE__ */ new Set([
2769
3065
  "map",
@@ -2861,20 +3157,20 @@ function isNode4(value) {
2861
3157
  }
2862
3158
  function isPureCall(node) {
2863
3159
  const callee = node.callee;
2864
- if (callee.type !== import_utils24.AST_NODE_TYPES.MemberExpression) {
3160
+ if (callee.type !== import_utils27.AST_NODE_TYPES.MemberExpression) {
2865
3161
  return false;
2866
3162
  }
2867
3163
  const property = callee.property;
2868
- if (property.type !== import_utils24.AST_NODE_TYPES.Identifier) {
3164
+ if (property.type !== import_utils27.AST_NODE_TYPES.Identifier) {
2869
3165
  return false;
2870
3166
  }
2871
- 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)) {
2872
3168
  return true;
2873
3169
  }
2874
3170
  return PURE_METHODS.has(property.name);
2875
3171
  }
2876
3172
  function isPureNew(node) {
2877
- 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);
2878
3174
  }
2879
3175
  function subtreeMatches(stmt, predicate) {
2880
3176
  let found = false;
@@ -2911,14 +3207,14 @@ function subtreeMatches(stmt, predicate) {
2911
3207
  visit(stmt);
2912
3208
  return found;
2913
3209
  }
2914
- 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);
2915
3211
  var hasThrowingCallOrNew = (stmt) => subtreeMatches(
2916
3212
  stmt,
2917
- (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)
2918
3214
  );
2919
3215
  function unwrap2(expr) {
2920
3216
  let current = expr;
2921
- 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) {
2922
3218
  current = current.expression;
2923
3219
  }
2924
3220
  return current;
@@ -2927,7 +3223,7 @@ function canThrow(stmt) {
2927
3223
  if (hasAwait(stmt)) {
2928
3224
  return true;
2929
3225
  }
2930
- 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) {
2931
3227
  return false;
2932
3228
  }
2933
3229
  return hasThrowingCallOrNew(stmt);
@@ -2938,9 +3234,9 @@ function handlerRethrows(handler) {
2938
3234
  }
2939
3235
  const body = handler.body.body;
2940
3236
  const last = body[body.length - 1];
2941
- 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;
2942
3238
  }
2943
- var no_fat_try_blocks_default = import_utils24.ESLintUtils.RuleCreator(
3239
+ var no_fat_try_blocks_default = import_utils27.ESLintUtils.RuleCreator(
2944
3240
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
2945
3241
  )({
2946
3242
  name: "no-fat-try-blocks",
@@ -2981,7 +3277,7 @@ var no_fat_try_blocks_default = import_utils24.ESLintUtils.RuleCreator(
2981
3277
  });
2982
3278
 
2983
3279
  // src/rules/no-secret-in-log.ts
2984
- var import_utils25 = require("@typescript-eslint/utils");
3280
+ var import_utils28 = require("@typescript-eslint/utils");
2985
3281
  var LOG_METHODS2 = /* @__PURE__ */ new Set([
2986
3282
  "debug",
2987
3283
  "info",
@@ -3179,7 +3475,7 @@ function propertyKeyName2(prop) {
3179
3475
  }
3180
3476
  return null;
3181
3477
  }
3182
- var no_secret_in_log_default = import_utils25.ESLintUtils.RuleCreator(
3478
+ var no_secret_in_log_default = import_utils28.ESLintUtils.RuleCreator(
3183
3479
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3184
3480
  )({
3185
3481
  name: "no-secret-in-log",
@@ -3247,15 +3543,15 @@ var no_secret_in_log_default = import_utils25.ESLintUtils.RuleCreator(
3247
3543
  });
3248
3544
 
3249
3545
  // src/rules/no-unsafe-cast.ts
3250
- var import_utils26 = require("@typescript-eslint/utils");
3251
- var import_utils27 = require("@typescript-eslint/utils");
3546
+ var import_utils29 = require("@typescript-eslint/utils");
3547
+ var import_utils30 = require("@typescript-eslint/utils");
3252
3548
  function isAnyAnnotation(node) {
3253
- return node.type === import_utils27.AST_NODE_TYPES.TSAnyKeyword;
3549
+ return node.type === import_utils30.AST_NODE_TYPES.TSAnyKeyword;
3254
3550
  }
3255
3551
  function isConstAssertion(typeAnnotation) {
3256
- 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";
3257
3553
  }
3258
- var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3554
+ var no_unsafe_cast_default = import_utils29.ESLintUtils.RuleCreator(
3259
3555
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3260
3556
  )({
3261
3557
  name: "no-unsafe-cast",
@@ -3281,7 +3577,7 @@ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3281
3577
  return;
3282
3578
  }
3283
3579
  const inner = node.expression;
3284
- 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) {
3285
3581
  context.report({ node, messageId: "doubleCast" });
3286
3582
  }
3287
3583
  }
@@ -3293,7 +3589,7 @@ var no_unsafe_cast_default = import_utils26.ESLintUtils.RuleCreator(
3293
3589
  });
3294
3590
 
3295
3591
  // src/rules/prefer-string-literal-union.ts
3296
- var import_utils28 = require("@typescript-eslint/utils");
3592
+ var import_utils31 = require("@typescript-eslint/utils");
3297
3593
  var ts = __toESM(require("typescript"), 1);
3298
3594
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
3299
3595
  "status",
@@ -3336,19 +3632,19 @@ function isChoiceLikeName(name) {
3336
3632
  return CHOICE_TOKENS.has(lastWord(name));
3337
3633
  }
3338
3634
  function keyName(key) {
3339
- if (key.type === import_utils28.AST_NODE_TYPES.Identifier) {
3635
+ if (key.type === import_utils31.AST_NODE_TYPES.Identifier) {
3340
3636
  return key.name;
3341
3637
  }
3342
- 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") {
3343
3639
  return key.value;
3344
3640
  }
3345
3641
  return null;
3346
3642
  }
3347
3643
  function isStringLiteralMember(t) {
3348
- 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";
3349
3645
  }
3350
3646
  function isStringLiteralUnion(node) {
3351
- if (node?.type !== import_utils28.AST_NODE_TYPES.TSUnionType) {
3647
+ if (node?.type !== import_utils31.AST_NODE_TYPES.TSUnionType) {
3352
3648
  return false;
3353
3649
  }
3354
3650
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -3377,12 +3673,12 @@ function bindingSourceExpression(decl) {
3377
3673
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
3378
3674
  }
3379
3675
  function refKey(node) {
3380
- if (node.type === import_utils28.AST_NODE_TYPES.Identifier) {
3676
+ if (node.type === import_utils31.AST_NODE_TYPES.Identifier) {
3381
3677
  return node.name;
3382
3678
  }
3383
- if (node.type === import_utils28.AST_NODE_TYPES.MemberExpression && !node.computed) {
3679
+ if (node.type === import_utils31.AST_NODE_TYPES.MemberExpression && !node.computed) {
3384
3680
  const inner = refKey(node.object);
3385
- 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) {
3386
3682
  return null;
3387
3683
  }
3388
3684
  return `${inner}.${node.property.name}`;
@@ -3390,12 +3686,12 @@ function refKey(node) {
3390
3686
  return null;
3391
3687
  }
3392
3688
  function strLiteral(node) {
3393
- 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") {
3394
3690
  return node.value;
3395
3691
  }
3396
3692
  return null;
3397
3693
  }
3398
- var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator(
3694
+ var prefer_string_literal_union_default = import_utils31.ESLintUtils.RuleCreator(
3399
3695
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3400
3696
  )({
3401
3697
  name: "prefer-string-literal-union",
@@ -3419,7 +3715,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3419
3715
  }
3420
3716
  let services;
3421
3717
  try {
3422
- services = import_utils28.ESLintUtils.getParserServices(context);
3718
+ services = import_utils31.ESLintUtils.getParserServices(context);
3423
3719
  } catch {
3424
3720
  services = null;
3425
3721
  }
@@ -3503,7 +3799,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3503
3799
  containersWithUnion.add(container);
3504
3800
  return;
3505
3801
  }
3506
- if (typeNode?.type !== import_utils28.AST_NODE_TYPES.TSStringKeyword) {
3802
+ if (typeNode?.type !== import_utils31.AST_NODE_TYPES.TSStringKeyword) {
3507
3803
  return;
3508
3804
  }
3509
3805
  const name = keyName(key);
@@ -3591,10 +3887,10 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3591
3887
  }
3592
3888
  };
3593
3889
  function refKeyText(node) {
3594
- if (node.type === import_utils28.AST_NODE_TYPES.BinaryExpression) {
3890
+ if (node.type === import_utils31.AST_NODE_TYPES.BinaryExpression) {
3595
3891
  return refKey(node.left) ?? refKey(node.right) ?? "value";
3596
3892
  }
3597
- if (node.type === import_utils28.AST_NODE_TYPES.SwitchStatement) {
3893
+ if (node.type === import_utils31.AST_NODE_TYPES.SwitchStatement) {
3598
3894
  return refKey(node.discriminant) ?? "value";
3599
3895
  }
3600
3896
  return "value";
@@ -3603,7 +3899,7 @@ var prefer_string_literal_union_default = import_utils28.ESLintUtils.RuleCreator
3603
3899
  });
3604
3900
 
3605
3901
  // src/rules/single-public-export.ts
3606
- var import_utils29 = require("@typescript-eslint/utils");
3902
+ var import_utils32 = require("@typescript-eslint/utils");
3607
3903
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
3608
3904
  "util",
3609
3905
  "utils",
@@ -3626,7 +3922,7 @@ var ACRONYM_OVERRIDES = [
3626
3922
  [/gRPC/g, "Grpc"]
3627
3923
  ];
3628
3924
  var CAMEL_BOUNDARY_RE = /(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g;
3629
- var TEST_FILE_RE = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3925
+ var TEST_FILE_RE2 = /\.(test|spec)\.[cm]?[jt]sx?$/i;
3630
3926
  var SCRIPT_EXT_RE = /\.[cm]?[jt]sx?$/i;
3631
3927
  var basename = (filename) => filename.split(/[/\\]/).pop() ?? filename;
3632
3928
  var stemOf = (base) => base.replace(SCRIPT_EXT_RE, "");
@@ -3637,12 +3933,12 @@ var kebabCase2 = (name) => {
3637
3933
  }
3638
3934
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
3639
3935
  };
3640
- 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);
3641
3937
  var functionConstName = (decl) => {
3642
3938
  if (decl.declarations.length !== 1) return null;
3643
3939
  const [declarator] = decl.declarations;
3644
3940
  if (declarator === void 0) return null;
3645
- 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;
3646
3942
  if (!isFunctionExpression(declarator.init)) return null;
3647
3943
  return declarator.id.name;
3648
3944
  };
@@ -3656,20 +3952,20 @@ var summarizeExports = (body) => {
3656
3952
  };
3657
3953
  for (const statement of body) {
3658
3954
  switch (statement.type) {
3659
- case import_utils29.AST_NODE_TYPES.ExportAllDeclaration:
3955
+ case import_utils32.AST_NODE_TYPES.ExportAllDeclaration:
3660
3956
  hasReExport = true;
3661
3957
  break;
3662
- case import_utils29.AST_NODE_TYPES.ExportDefaultDeclaration: {
3958
+ case import_utils32.AST_NODE_TYPES.ExportDefaultDeclaration: {
3663
3959
  names += 1;
3664
3960
  const decl = statement.declaration;
3665
- 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) {
3666
3962
  candidate = { name: decl.id.name, node: statement };
3667
- } 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) {
3668
3964
  candidate = { name: decl.id.name, node: statement };
3669
3965
  }
3670
3966
  break;
3671
3967
  }
3672
- case import_utils29.AST_NODE_TYPES.ExportNamedDeclaration: {
3968
+ case import_utils32.AST_NODE_TYPES.ExportNamedDeclaration: {
3673
3969
  if (statement.source !== null) {
3674
3970
  hasReExport = true;
3675
3971
  break;
@@ -3680,15 +3976,15 @@ var summarizeExports = (body) => {
3680
3976
  break;
3681
3977
  }
3682
3978
  switch (decl.type) {
3683
- case import_utils29.AST_NODE_TYPES.FunctionDeclaration:
3979
+ case import_utils32.AST_NODE_TYPES.FunctionDeclaration:
3684
3980
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3685
3981
  else names += 1;
3686
3982
  break;
3687
- case import_utils29.AST_NODE_TYPES.ClassDeclaration:
3983
+ case import_utils32.AST_NODE_TYPES.ClassDeclaration:
3688
3984
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3689
3985
  else names += 1;
3690
3986
  break;
3691
- case import_utils29.AST_NODE_TYPES.VariableDeclaration: {
3987
+ case import_utils32.AST_NODE_TYPES.VariableDeclaration: {
3692
3988
  const fnName = functionConstName(decl);
3693
3989
  if (fnName !== null && decl.declarations.length === 1) {
3694
3990
  addCandidate(fnName, statement);
@@ -3708,7 +4004,7 @@ var summarizeExports = (body) => {
3708
4004
  }
3709
4005
  return { names, hasReExport, candidate };
3710
4006
  };
3711
- var single_public_export_default = import_utils29.ESLintUtils.RuleCreator(
4007
+ var single_public_export_default = import_utils32.ESLintUtils.RuleCreator(
3712
4008
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3713
4009
  )({
3714
4010
  name: "single-public-export",
@@ -3726,7 +4022,7 @@ var single_public_export_default = import_utils29.ESLintUtils.RuleCreator(
3726
4022
  create(context) {
3727
4023
  const base = basename(context.filename);
3728
4024
  if (base.endsWith(".d.ts")) return {};
3729
- if (TEST_FILE_RE.test(base)) return {};
4025
+ if (TEST_FILE_RE2.test(base)) return {};
3730
4026
  const stem = stemOf(base);
3731
4027
  if (!JUNK_DRAWER_STEMS.has(stem.toLowerCase())) return {};
3732
4028
  return {
@@ -3774,12 +4070,15 @@ var rules = {
3774
4070
  "no-secret-in-log": no_secret_in_log_default,
3775
4071
  "no-unsafe-cast": no_unsafe_cast_default,
3776
4072
  "prefer-string-literal-union": prefer_string_literal_union_default,
3777
- "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
3778
4077
  };
3779
4078
  var plugin = {
3780
4079
  meta: {
3781
4080
  name: "@sarj/eslint-plugin",
3782
- version: "2.5.0"
4081
+ version: "2.7.0"
3783
4082
  },
3784
4083
  rules,
3785
4084
  configs: {
@@ -3811,7 +4110,11 @@ var plugin = {
3811
4110
  "@sarj/no-secret-in-log": "warn",
3812
4111
  "@sarj/no-unsafe-cast": "warn",
3813
4112
  "@sarj/single-public-export": "warn",
3814
- "@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"
3815
4118
  }
3816
4119
  },
3817
4120
  strict: {
@@ -3847,7 +4150,11 @@ var plugin = {
3847
4150
  "@sarj/no-unsafe-cast": "warn",
3848
4151
  "@sarj/single-public-export": "error",
3849
4152
  // High-volume/stylistic — warn until rollout proves FP rate.
3850
- "@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"
3851
4158
  }
3852
4159
  }
3853
4160
  }