@sarj/eslint-plugin 2.4.0 → 2.5.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.d.cts CHANGED
@@ -72,6 +72,9 @@ declare const rules: {
72
72
  "no-secret-in-log": _typescript_eslint_utils_ts_eslint.RuleModule<"noSecretInLog", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
73
73
  name: string;
74
74
  };
75
+ "no-unsafe-cast": _typescript_eslint_utils_ts_eslint.RuleModule<"asAny" | "doubleCast", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
76
+ name: string;
77
+ };
75
78
  "prefer-string-literal-union": _typescript_eslint_utils_ts_eslint.RuleModule<"bareChoiceField" | "comparisonCluster", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
76
79
  name: string;
77
80
  };
@@ -156,6 +159,9 @@ declare const plugin: {
156
159
  "no-secret-in-log": _typescript_eslint_utils_ts_eslint.RuleModule<"noSecretInLog", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
157
160
  name: string;
158
161
  };
162
+ "no-unsafe-cast": _typescript_eslint_utils_ts_eslint.RuleModule<"asAny" | "doubleCast", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
163
+ name: string;
164
+ };
159
165
  "prefer-string-literal-union": _typescript_eslint_utils_ts_eslint.RuleModule<"bareChoiceField" | "comparisonCluster", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
160
166
  name: string;
161
167
  };
@@ -187,6 +193,7 @@ declare const plugin: {
187
193
  "@sarj/no-fat-try-blocks": string;
188
194
  "@sarj/no-cors-wildcard-with-credentials": string;
189
195
  "@sarj/no-secret-in-log": string;
196
+ "@sarj/no-unsafe-cast": string;
190
197
  "@sarj/single-public-export": string;
191
198
  "@sarj/prefer-string-literal-union": string;
192
199
  };
@@ -217,6 +224,7 @@ declare const plugin: {
217
224
  "@sarj/no-fat-try-blocks": string;
218
225
  "@sarj/no-cors-wildcard-with-credentials": string;
219
226
  "@sarj/no-secret-in-log": string;
227
+ "@sarj/no-unsafe-cast": string;
220
228
  "@sarj/single-public-export": string;
221
229
  "@sarj/prefer-string-literal-union": string;
222
230
  };
package/dist/index.d.ts CHANGED
@@ -72,6 +72,9 @@ declare const rules: {
72
72
  "no-secret-in-log": _typescript_eslint_utils_ts_eslint.RuleModule<"noSecretInLog", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
73
73
  name: string;
74
74
  };
75
+ "no-unsafe-cast": _typescript_eslint_utils_ts_eslint.RuleModule<"asAny" | "doubleCast", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
76
+ name: string;
77
+ };
75
78
  "prefer-string-literal-union": _typescript_eslint_utils_ts_eslint.RuleModule<"bareChoiceField" | "comparisonCluster", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
76
79
  name: string;
77
80
  };
@@ -156,6 +159,9 @@ declare const plugin: {
156
159
  "no-secret-in-log": _typescript_eslint_utils_ts_eslint.RuleModule<"noSecretInLog", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
157
160
  name: string;
158
161
  };
162
+ "no-unsafe-cast": _typescript_eslint_utils_ts_eslint.RuleModule<"asAny" | "doubleCast", [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
163
+ name: string;
164
+ };
159
165
  "prefer-string-literal-union": _typescript_eslint_utils_ts_eslint.RuleModule<"bareChoiceField" | "comparisonCluster", readonly [], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
160
166
  name: string;
161
167
  };
@@ -187,6 +193,7 @@ declare const plugin: {
187
193
  "@sarj/no-fat-try-blocks": string;
188
194
  "@sarj/no-cors-wildcard-with-credentials": string;
189
195
  "@sarj/no-secret-in-log": string;
196
+ "@sarj/no-unsafe-cast": string;
190
197
  "@sarj/single-public-export": string;
191
198
  "@sarj/prefer-string-literal-union": string;
192
199
  };
@@ -217,6 +224,7 @@ declare const plugin: {
217
224
  "@sarj/no-fat-try-blocks": string;
218
225
  "@sarj/no-cors-wildcard-with-credentials": string;
219
226
  "@sarj/no-secret-in-log": string;
227
+ "@sarj/no-unsafe-cast": string;
220
228
  "@sarj/single-public-export": string;
221
229
  "@sarj/prefer-string-literal-union": string;
222
230
  };
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  // src/rules/enforce-file-structure.ts
2
2
  import { ESLintUtils, AST_NODE_TYPES } from "@typescript-eslint/utils";
3
- var SERVER_ACTION_FILE_RE = /(?:^|\/)actions\/|\.action\.[jt]sx?$|(?:^|\/)actions\.[jt]sx?$/;
4
3
  var classifyStatement = (statement) => {
5
4
  switch (statement.type) {
6
5
  case AST_NODE_TYPES.ImportDeclaration:
@@ -15,7 +14,6 @@ var classifyStatement = (statement) => {
15
14
  };
16
15
  var isStringDirective = (statement) => statement.type === AST_NODE_TYPES.ExpressionStatement && statement.expression.type === AST_NODE_TYPES.Literal && typeof statement.expression.value === "string" && statement.expression.value.startsWith("use ");
17
16
  var isUseServerDirective = (statement) => {
18
- if (statement === void 0) return false;
19
17
  if (statement.type !== AST_NODE_TYPES.ExpressionStatement) return false;
20
18
  const expr = statement.expression;
21
19
  if (expr.type !== AST_NODE_TYPES.Literal) return false;
@@ -28,23 +26,25 @@ var enforce_file_structure_default = ESLintUtils.RuleCreator(
28
26
  meta: {
29
27
  type: "suggestion",
30
28
  docs: {
31
- description: "Require `import` statements to come first, then allow step-down ordering (public API first, private helpers below) for the rest of the file. Exported statements are classified by WHAT they export \u2014 an exported interface is a declaration, an exported function is a function \u2014 so a public exported function followed by a private helper, or an exported interface among declarations, is allowed. Re-exports (`export { \u2026 } from`, `export *`, `export { \u2026 }`) are a neutral group, so generated namespace barrels pass. Server-action files (under `/actions/`, named `*.action.ts`, or `actions.ts`) must also begin with a `use server` directive."
29
+ description: "Require `import` statements to come first, then allow step-down ordering (public API first, private helpers below) for the rest of the file. Exported statements are classified by WHAT they export \u2014 an exported interface is a declaration, an exported function is a function \u2014 so a public exported function followed by a private helper, or an exported interface among declarations, is allowed. Re-exports (`export { \u2026 } from`, `export *`, `export { \u2026 }`) are a neutral group, so generated namespace barrels pass. When a module contains a `use server` directive, it must be the first statement in the file."
32
30
  },
33
31
  schema: [],
34
32
  messages: {
35
33
  importsFirst: "File structure violation: import statements must come before other declarations",
36
- useServerDirective: "Server action files must start with 'use server' directive"
34
+ useServerDirective: "A 'use server' directive must be the first statement in the file"
37
35
  }
38
36
  },
39
37
  defaultOptions: [],
40
38
  create(context) {
41
- const isServerAction = SERVER_ACTION_FILE_RE.test(context.filename);
42
39
  return {
43
40
  Program(node) {
44
41
  const body = node.body;
45
- if (isServerAction && !isUseServerDirective(body[0])) {
42
+ const misplacedUseServer = body.find(
43
+ (statement, index) => index > 0 && isUseServerDirective(statement)
44
+ );
45
+ if (misplacedUseServer !== void 0) {
46
46
  context.report({
47
- node,
47
+ node: misplacedUseServer,
48
48
  messageId: "useServerDirective"
49
49
  });
50
50
  }
@@ -629,12 +629,49 @@ function instanceofErrorSubject(test) {
629
629
  }
630
630
  return null;
631
631
  }
632
+ var TYPE_GUARD_PATTERN = /^(is|has)[A-Z]/;
633
+ function typeGuardSubject(test) {
634
+ const arg = test.type === "CallExpression" ? test.arguments[0] : void 0;
635
+ if (test.type === "CallExpression" && test.callee.type === "Identifier" && TYPE_GUARD_PATTERN.test(test.callee.name) && test.arguments.length === 1 && arg !== void 0 && arg.type !== "SpreadElement") {
636
+ return arg;
637
+ }
638
+ return null;
639
+ }
640
+ function positiveErrorSubject(test) {
641
+ return instanceofErrorSubject(test) ?? typeGuardSubject(test);
642
+ }
632
643
  function negatedInstanceofErrorSubject(test) {
633
644
  if (test.type === "UnaryExpression" && test.operator === "!") {
634
- return instanceofErrorSubject(test.argument);
645
+ return positiveErrorSubject(test.argument);
635
646
  }
636
647
  return null;
637
648
  }
649
+ function branchTerminates(branch) {
650
+ const body = branch.type === "BlockStatement" ? branch.body : [branch];
651
+ const last = body[body.length - 1];
652
+ return last !== void 0 && (last.type === "ReturnStatement" || last.type === "ThrowStatement");
653
+ }
654
+ function isNarrowedByEarlyReturn(node, argExpr, sourceCode) {
655
+ const argText = sourceCode.getText(argExpr);
656
+ let current = node.parent;
657
+ while (current) {
658
+ if (current.type === "BlockStatement" || current.type === "Program") {
659
+ for (const stmt of current.body) {
660
+ if (stmt.range[0] >= node.range[0]) {
661
+ break;
662
+ }
663
+ if (stmt.type === "IfStatement" && stmt.alternate === null && branchTerminates(stmt.consequent)) {
664
+ const subject = positiveErrorSubject(stmt.test);
665
+ if (subject && sourceCode.getText(subject) === argText) {
666
+ return true;
667
+ }
668
+ }
669
+ }
670
+ }
671
+ current = current.parent;
672
+ }
673
+ return false;
674
+ }
638
675
  function nodeWithin(node, container) {
639
676
  return container !== null && node.range[0] >= container.range[0] && node.range[1] <= container.range[1];
640
677
  }
@@ -644,7 +681,7 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
644
681
  let current = node.parent;
645
682
  while (current) {
646
683
  if (current.type === "ConditionalExpression") {
647
- const subject = instanceofErrorSubject(current.test);
684
+ const subject = positiveErrorSubject(current.test);
648
685
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
649
686
  return true;
650
687
  }
@@ -653,7 +690,7 @@ function isGuardedByInstanceofError(node, argExpr, sourceCode) {
653
690
  return true;
654
691
  }
655
692
  } else if (current.type === "IfStatement") {
656
- const subject = instanceofErrorSubject(current.test);
693
+ const subject = positiveErrorSubject(current.test);
657
694
  if (subject && sameSubject(subject) && nodeWithin(node, current.alternate)) {
658
695
  return true;
659
696
  }
@@ -706,7 +743,7 @@ var no_json_stringify_error_default = ESLintUtils6.RuleCreator(
706
743
  if (!suggestsError) {
707
744
  return;
708
745
  }
709
- if (isGuardedByInstanceofError(node, firstArg, context.sourceCode)) {
746
+ if (isGuardedByInstanceofError(node, firstArg, context.sourceCode) || isNarrowedByEarlyReturn(node, firstArg, context.sourceCode)) {
710
747
  return;
711
748
  }
712
749
  context.report({
@@ -849,6 +886,17 @@ function isProcessEnv(node) {
849
886
  function isImportMetaEnv(node) {
850
887
  return !node.computed && node.property.type === "Identifier" && node.property.name === "env" && node.object.type === "MetaProperty" && node.object.meta.name === "import" && node.object.property.name === "meta";
851
888
  }
889
+ var BUILD_TIME_CONSTANTS = /* @__PURE__ */ new Set([
890
+ "NODE_ENV",
891
+ "MODE",
892
+ "DEV",
893
+ "PROD",
894
+ "SSR"
895
+ ]);
896
+ function isBuildTimeConstantAccess(node) {
897
+ const parent = node.parent;
898
+ return parent.type === "MemberExpression" && parent.object === node && !parent.computed && parent.property.type === "Identifier" && BUILD_TIME_CONSTANTS.has(parent.property.name);
899
+ }
852
900
  var no_raw_env_default = ESLintUtils8.RuleCreator(
853
901
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
854
902
  )({
@@ -867,7 +915,7 @@ var no_raw_env_default = ESLintUtils8.RuleCreator(
867
915
  create(context) {
868
916
  return {
869
917
  MemberExpression(node) {
870
- if (isProcessEnv(node) || isImportMetaEnv(node)) {
918
+ if ((isProcessEnv(node) || isImportMetaEnv(node)) && !isBuildTimeConstantAccess(node)) {
871
919
  context.report({
872
920
  node,
873
921
  messageId: "noRawEnv"
@@ -1106,7 +1154,7 @@ var no_sentinel_return_on_catch_default = ESLintUtils9.RuleCreator(
1106
1154
  // src/rules/no-sequential-await.ts
1107
1155
  import { ESLintUtils as ESLintUtils10 } from "@typescript-eslint/utils";
1108
1156
  var ARRAY_ITERATION_METHODS = /* @__PURE__ */ new Set(["forEach", "map", "filter"]);
1109
- var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain/i;
1157
+ var SEQUENTIAL_ITERABLE_HINT = /sort|reverse|ordered|sequence|hook|middleware|pipeline|\bstage|\bstep|\bphase|migration|chain|buffer|stream|teleport|chunk|\bqueue|drain/i;
1110
1158
  function isFunctionLike(node) {
1111
1159
  return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
1112
1160
  }
@@ -1149,9 +1197,29 @@ function hasEarlyExit(root) {
1149
1197
  });
1150
1198
  return found;
1151
1199
  }
1200
+ var TIMER_HELPER_RE = /^(sleep|timeout|delay|wait|pause|tick)$/i;
1201
+ function calleeName2(callee) {
1202
+ if (callee.type === "Identifier") return callee.name;
1203
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
1204
+ return callee.property.name;
1205
+ }
1206
+ return null;
1207
+ }
1152
1208
  function isTimerYield(node) {
1153
1209
  const arg = node.argument;
1154
- return arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise";
1210
+ if (arg.type === "NewExpression" && arg.callee.type === "Identifier" && arg.callee.name === "Promise") {
1211
+ return true;
1212
+ }
1213
+ if (arg.type === "CallExpression") {
1214
+ const name = calleeName2(arg.callee);
1215
+ return name !== null && TIMER_HELPER_RE.test(name);
1216
+ }
1217
+ return false;
1218
+ }
1219
+ var QUEUE_DRAIN_METHODS = /^(shift|pop|dequeue|next|poll)$/;
1220
+ function isQueueDrain(node) {
1221
+ const arg = node.argument;
1222
+ return arg.type === "CallExpression" && arg.callee.type === "MemberExpression" && !arg.callee.computed && arg.callee.property.type === "Identifier" && QUEUE_DRAIN_METHODS.test(arg.callee.property.name);
1155
1223
  }
1156
1224
  function referencesName(root, name) {
1157
1225
  let found = false;
@@ -1186,7 +1254,7 @@ function shouldReport(awaits, earlyExit, iterableText) {
1186
1254
  return false;
1187
1255
  }
1188
1256
  return awaits.some(
1189
- (node) => !isTimerYield(node) && !isThreadedAccumulator(node)
1257
+ (node) => !isTimerYield(node) && !isThreadedAccumulator(node) && !isQueueDrain(node)
1190
1258
  );
1191
1259
  }
1192
1260
  var no_sequential_await_default = ESLintUtils10.RuleCreator(
@@ -1207,10 +1275,10 @@ var no_sequential_await_default = ESLintUtils10.RuleCreator(
1207
1275
  create(context) {
1208
1276
  function loopParts(node) {
1209
1277
  if (node.type === "ForStatement") {
1210
- return [node.body, node.init, node.test, node.update];
1278
+ return [node.body, node.test, node.update];
1211
1279
  }
1212
1280
  if (node.type === "ForOfStatement" || node.type === "ForInStatement") {
1213
- return [node.body, node.right];
1281
+ return [node.body];
1214
1282
  }
1215
1283
  return [node.body, node.test];
1216
1284
  }
@@ -1872,10 +1940,10 @@ var SVG_EXEMPT_COLOR_VALUES = /* @__PURE__ */ new Set([
1872
1940
  "currentcolor",
1873
1941
  "inherit"
1874
1942
  ]);
1875
- var isInsideSvgDefsContainer = (node) => {
1943
+ var isInsideSvg = (node) => {
1876
1944
  let current = node.parent;
1877
1945
  while (current !== void 0 && current !== null) {
1878
- if (current.type === AST_NODE_TYPES7.JSXElement && current.openingElement.name.type === AST_NODE_TYPES7.JSXIdentifier && SVG_DEFS_CONTAINERS.has(current.openingElement.name.name)) {
1946
+ if (current.type === AST_NODE_TYPES7.JSXElement && current.openingElement.name.type === AST_NODE_TYPES7.JSXIdentifier && (current.openingElement.name.name === "svg" || SVG_DEFS_CONTAINERS.has(current.openingElement.name.name))) {
1879
1947
  return true;
1880
1948
  }
1881
1949
  current = current.parent;
@@ -1985,7 +2053,7 @@ var prefer_semantic_colors_default = ESLintUtils15.RuleCreator(
1985
2053
  if (typeof node.value.value === "string" && SVG_EXEMPT_COLOR_VALUES.has(node.value.value.toLowerCase())) {
1986
2054
  return;
1987
2055
  }
1988
- if (isInsideSvgDefsContainer(node)) return;
2056
+ if (isInsideSvg(node)) return;
1989
2057
  checkColorValueNode(node.value);
1990
2058
  },
1991
2059
  // Inline style objects: style={{ color: "#111827", backgroundColor: "#fff" }}
@@ -2519,7 +2587,7 @@ function propertyKeyName(prop) {
2519
2587
  }
2520
2588
  return void 0;
2521
2589
  }
2522
- function calleeName2(node) {
2590
+ function calleeName3(node) {
2523
2591
  const callee = node.callee;
2524
2592
  if (callee.type === "Identifier") {
2525
2593
  return callee.name;
@@ -2530,7 +2598,7 @@ function calleeName2(node) {
2530
2598
  return void 0;
2531
2599
  }
2532
2600
  function isCorsWildcardCredentialsCall(node) {
2533
- const name = calleeName2(node);
2601
+ const name = calleeName3(node);
2534
2602
  if (name === void 0 || name.toLowerCase() !== "cors") {
2535
2603
  return false;
2536
2604
  }
@@ -3162,10 +3230,56 @@ var no_secret_in_log_default = ESLintUtils23.RuleCreator(
3162
3230
  }
3163
3231
  });
3164
3232
 
3233
+ // 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";
3236
+ function isAnyAnnotation(node) {
3237
+ return node.type === AST_NODE_TYPES13.TSAnyKeyword;
3238
+ }
3239
+ function isConstAssertion(typeAnnotation) {
3240
+ return typeAnnotation.type === AST_NODE_TYPES13.TSTypeReference && typeAnnotation.typeName.type === AST_NODE_TYPES13.Identifier && typeAnnotation.typeName.name === "const";
3241
+ }
3242
+ var no_unsafe_cast_default = ESLintUtils24.RuleCreator(
3243
+ (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3244
+ )({
3245
+ name: "no-unsafe-cast",
3246
+ meta: {
3247
+ type: "problem",
3248
+ docs: {
3249
+ description: "Disallow `as any`/`<any>` casts and `as unknown as T` double-casts, which fully disable type checking."
3250
+ },
3251
+ schema: [],
3252
+ messages: {
3253
+ asAny: "`as any` disables type checking on this value. Narrow with a type guard, model the shape, or cast to a specific type instead.",
3254
+ doubleCast: "`as unknown as T` double-cast bypasses the compiler's structural check. Prefer a runtime validator (e.g. a zod schema) or an accurate type at the boundary."
3255
+ }
3256
+ },
3257
+ defaultOptions: [],
3258
+ create(context) {
3259
+ function checkAssertion(node) {
3260
+ if (isConstAssertion(node.typeAnnotation)) {
3261
+ return;
3262
+ }
3263
+ if (isAnyAnnotation(node.typeAnnotation)) {
3264
+ context.report({ node, messageId: "asAny" });
3265
+ return;
3266
+ }
3267
+ const inner = node.expression;
3268
+ if (inner.type === AST_NODE_TYPES13.TSAsExpression || inner.type === AST_NODE_TYPES13.TSTypeAssertion) {
3269
+ context.report({ node, messageId: "doubleCast" });
3270
+ }
3271
+ }
3272
+ return {
3273
+ TSAsExpression: checkAssertion,
3274
+ TSTypeAssertion: checkAssertion
3275
+ };
3276
+ }
3277
+ });
3278
+
3165
3279
  // src/rules/prefer-string-literal-union.ts
3166
3280
  import {
3167
- ESLintUtils as ESLintUtils24,
3168
- AST_NODE_TYPES as AST_NODE_TYPES13
3281
+ ESLintUtils as ESLintUtils25,
3282
+ AST_NODE_TYPES as AST_NODE_TYPES14
3169
3283
  } from "@typescript-eslint/utils";
3170
3284
  import * as ts from "typescript";
3171
3285
  var CHOICE_TOKENS = /* @__PURE__ */ new Set([
@@ -3209,19 +3323,19 @@ function isChoiceLikeName(name) {
3209
3323
  return CHOICE_TOKENS.has(lastWord(name));
3210
3324
  }
3211
3325
  function keyName(key) {
3212
- if (key.type === AST_NODE_TYPES13.Identifier) {
3326
+ if (key.type === AST_NODE_TYPES14.Identifier) {
3213
3327
  return key.name;
3214
3328
  }
3215
- if (key.type === AST_NODE_TYPES13.Literal && typeof key.value === "string") {
3329
+ if (key.type === AST_NODE_TYPES14.Literal && typeof key.value === "string") {
3216
3330
  return key.value;
3217
3331
  }
3218
3332
  return null;
3219
3333
  }
3220
3334
  function isStringLiteralMember(t) {
3221
- return t.type === AST_NODE_TYPES13.TSLiteralType && t.literal.type === AST_NODE_TYPES13.Literal && typeof t.literal.value === "string";
3335
+ return t.type === AST_NODE_TYPES14.TSLiteralType && t.literal.type === AST_NODE_TYPES14.Literal && typeof t.literal.value === "string";
3222
3336
  }
3223
3337
  function isStringLiteralUnion(node) {
3224
- if (node?.type !== AST_NODE_TYPES13.TSUnionType) {
3338
+ if (node?.type !== AST_NODE_TYPES14.TSUnionType) {
3225
3339
  return false;
3226
3340
  }
3227
3341
  return node.types.filter(isStringLiteralMember).length >= MIN_CLUSTER_SIZE;
@@ -3250,12 +3364,12 @@ function bindingSourceExpression(decl) {
3250
3364
  return ts.isForOfStatement(node) ? node.expression : node.initializer;
3251
3365
  }
3252
3366
  function refKey(node) {
3253
- if (node.type === AST_NODE_TYPES13.Identifier) {
3367
+ if (node.type === AST_NODE_TYPES14.Identifier) {
3254
3368
  return node.name;
3255
3369
  }
3256
- if (node.type === AST_NODE_TYPES13.MemberExpression && !node.computed) {
3370
+ if (node.type === AST_NODE_TYPES14.MemberExpression && !node.computed) {
3257
3371
  const inner = refKey(node.object);
3258
- if (inner === null || node.property.type !== AST_NODE_TYPES13.Identifier) {
3372
+ if (inner === null || node.property.type !== AST_NODE_TYPES14.Identifier) {
3259
3373
  return null;
3260
3374
  }
3261
3375
  return `${inner}.${node.property.name}`;
@@ -3263,12 +3377,12 @@ function refKey(node) {
3263
3377
  return null;
3264
3378
  }
3265
3379
  function strLiteral(node) {
3266
- if (node.type === AST_NODE_TYPES13.Literal && typeof node.value === "string") {
3380
+ if (node.type === AST_NODE_TYPES14.Literal && typeof node.value === "string") {
3267
3381
  return node.value;
3268
3382
  }
3269
3383
  return null;
3270
3384
  }
3271
- var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
3385
+ var prefer_string_literal_union_default = ESLintUtils25.RuleCreator(
3272
3386
  (name) => `https://github.com/sarj-ai/linting/blob/main/packages/typescript/src/rules/${name}.ts`
3273
3387
  )({
3274
3388
  name: "prefer-string-literal-union",
@@ -3292,7 +3406,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
3292
3406
  }
3293
3407
  let services;
3294
3408
  try {
3295
- services = ESLintUtils24.getParserServices(context);
3409
+ services = ESLintUtils25.getParserServices(context);
3296
3410
  } catch {
3297
3411
  services = null;
3298
3412
  }
@@ -3376,7 +3490,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
3376
3490
  containersWithUnion.add(container);
3377
3491
  return;
3378
3492
  }
3379
- if (typeNode?.type !== AST_NODE_TYPES13.TSStringKeyword) {
3493
+ if (typeNode?.type !== AST_NODE_TYPES14.TSStringKeyword) {
3380
3494
  return;
3381
3495
  }
3382
3496
  const name = keyName(key);
@@ -3464,10 +3578,10 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
3464
3578
  }
3465
3579
  };
3466
3580
  function refKeyText(node) {
3467
- if (node.type === AST_NODE_TYPES13.BinaryExpression) {
3581
+ if (node.type === AST_NODE_TYPES14.BinaryExpression) {
3468
3582
  return refKey(node.left) ?? refKey(node.right) ?? "value";
3469
3583
  }
3470
- if (node.type === AST_NODE_TYPES13.SwitchStatement) {
3584
+ if (node.type === AST_NODE_TYPES14.SwitchStatement) {
3471
3585
  return refKey(node.discriminant) ?? "value";
3472
3586
  }
3473
3587
  return "value";
@@ -3476,7 +3590,7 @@ var prefer_string_literal_union_default = ESLintUtils24.RuleCreator(
3476
3590
  });
3477
3591
 
3478
3592
  // src/rules/single-public-export.ts
3479
- import { ESLintUtils as ESLintUtils25, AST_NODE_TYPES as AST_NODE_TYPES14 } from "@typescript-eslint/utils";
3593
+ import { ESLintUtils as ESLintUtils26, AST_NODE_TYPES as AST_NODE_TYPES15 } from "@typescript-eslint/utils";
3480
3594
  var JUNK_DRAWER_STEMS = /* @__PURE__ */ new Set([
3481
3595
  "util",
3482
3596
  "utils",
@@ -3510,12 +3624,12 @@ var kebabCase2 = (name) => {
3510
3624
  }
3511
3625
  return normalized.replace(CAMEL_BOUNDARY_RE, "-").toLowerCase();
3512
3626
  };
3513
- var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES14.ArrowFunctionExpression || node.type === AST_NODE_TYPES14.FunctionExpression);
3627
+ var isFunctionExpression = (node) => node !== null && (node.type === AST_NODE_TYPES15.ArrowFunctionExpression || node.type === AST_NODE_TYPES15.FunctionExpression);
3514
3628
  var functionConstName = (decl) => {
3515
3629
  if (decl.declarations.length !== 1) return null;
3516
3630
  const [declarator] = decl.declarations;
3517
3631
  if (declarator === void 0) return null;
3518
- if (declarator.id.type !== AST_NODE_TYPES14.Identifier) return null;
3632
+ if (declarator.id.type !== AST_NODE_TYPES15.Identifier) return null;
3519
3633
  if (!isFunctionExpression(declarator.init)) return null;
3520
3634
  return declarator.id.name;
3521
3635
  };
@@ -3529,20 +3643,20 @@ var summarizeExports = (body) => {
3529
3643
  };
3530
3644
  for (const statement of body) {
3531
3645
  switch (statement.type) {
3532
- case AST_NODE_TYPES14.ExportAllDeclaration:
3646
+ case AST_NODE_TYPES15.ExportAllDeclaration:
3533
3647
  hasReExport = true;
3534
3648
  break;
3535
- case AST_NODE_TYPES14.ExportDefaultDeclaration: {
3649
+ case AST_NODE_TYPES15.ExportDefaultDeclaration: {
3536
3650
  names += 1;
3537
3651
  const decl = statement.declaration;
3538
- if (decl.type === AST_NODE_TYPES14.FunctionDeclaration && decl.id !== null) {
3652
+ if (decl.type === AST_NODE_TYPES15.FunctionDeclaration && decl.id !== null) {
3539
3653
  candidate = { name: decl.id.name, node: statement };
3540
- } else if (decl.type === AST_NODE_TYPES14.ClassDeclaration && decl.id !== null) {
3654
+ } else if (decl.type === AST_NODE_TYPES15.ClassDeclaration && decl.id !== null) {
3541
3655
  candidate = { name: decl.id.name, node: statement };
3542
3656
  }
3543
3657
  break;
3544
3658
  }
3545
- case AST_NODE_TYPES14.ExportNamedDeclaration: {
3659
+ case AST_NODE_TYPES15.ExportNamedDeclaration: {
3546
3660
  if (statement.source !== null) {
3547
3661
  hasReExport = true;
3548
3662
  break;
@@ -3553,15 +3667,15 @@ var summarizeExports = (body) => {
3553
3667
  break;
3554
3668
  }
3555
3669
  switch (decl.type) {
3556
- case AST_NODE_TYPES14.FunctionDeclaration:
3670
+ case AST_NODE_TYPES15.FunctionDeclaration:
3557
3671
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3558
3672
  else names += 1;
3559
3673
  break;
3560
- case AST_NODE_TYPES14.ClassDeclaration:
3674
+ case AST_NODE_TYPES15.ClassDeclaration:
3561
3675
  if (decl.id !== null) addCandidate(decl.id.name, statement);
3562
3676
  else names += 1;
3563
3677
  break;
3564
- case AST_NODE_TYPES14.VariableDeclaration: {
3678
+ case AST_NODE_TYPES15.VariableDeclaration: {
3565
3679
  const fnName = functionConstName(decl);
3566
3680
  if (fnName !== null && decl.declarations.length === 1) {
3567
3681
  addCandidate(fnName, statement);
@@ -3581,7 +3695,7 @@ var summarizeExports = (body) => {
3581
3695
  }
3582
3696
  return { names, hasReExport, candidate };
3583
3697
  };
3584
- var single_public_export_default = ESLintUtils25.RuleCreator(
3698
+ var single_public_export_default = ESLintUtils26.RuleCreator(
3585
3699
  (name) => `https://github.com/sarj-ai/standards/blob/main/packages/typescript/src/rules/${name}.ts`
3586
3700
  )({
3587
3701
  name: "single-public-export",
@@ -3645,13 +3759,14 @@ var rules = {
3645
3759
  "no-cors-wildcard-with-credentials": no_cors_wildcard_with_credentials_default,
3646
3760
  "no-fat-try-blocks": no_fat_try_blocks_default,
3647
3761
  "no-secret-in-log": no_secret_in_log_default,
3762
+ "no-unsafe-cast": no_unsafe_cast_default,
3648
3763
  "prefer-string-literal-union": prefer_string_literal_union_default,
3649
3764
  "single-public-export": single_public_export_default
3650
3765
  };
3651
3766
  var plugin = {
3652
3767
  meta: {
3653
3768
  name: "@sarj/eslint-plugin",
3654
- version: "2.4.0"
3769
+ version: "2.5.0"
3655
3770
  },
3656
3771
  rules,
3657
3772
  configs: {
@@ -3681,6 +3796,7 @@ var plugin = {
3681
3796
  "@sarj/no-fat-try-blocks": "warn",
3682
3797
  "@sarj/no-cors-wildcard-with-credentials": "warn",
3683
3798
  "@sarj/no-secret-in-log": "warn",
3799
+ "@sarj/no-unsafe-cast": "warn",
3684
3800
  "@sarj/single-public-export": "warn",
3685
3801
  "@sarj/prefer-string-literal-union": "warn"
3686
3802
  }
@@ -3715,6 +3831,7 @@ var plugin = {
3715
3831
  "@sarj/no-fat-try-blocks": "error",
3716
3832
  "@sarj/no-cors-wildcard-with-credentials": "error",
3717
3833
  "@sarj/no-secret-in-log": "error",
3834
+ "@sarj/no-unsafe-cast": "warn",
3718
3835
  "@sarj/single-public-export": "error",
3719
3836
  // High-volume/stylistic — warn until rollout proves FP rate.
3720
3837
  "@sarj/prefer-string-literal-union": "warn"