eslint-plugin-ai-guard 1.1.13 → 1.2.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
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -28,7 +38,7 @@ __export(index_exports, {
28
38
  });
29
39
  module.exports = __toCommonJS(index_exports);
30
40
 
31
- // src/rules/error-handling/no-empty-catch.ts
41
+ // src/rules/reliability/no-empty-catch.ts
32
42
  var import_utils = require("@typescript-eslint/utils");
33
43
  var createRule = import_utils.ESLintUtils.RuleCreator(
34
44
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
@@ -75,7 +85,7 @@ var noEmptyCatch = createRule({
75
85
  }
76
86
  });
77
87
 
78
- // src/rules/error-handling/no-broad-exception.ts
88
+ // src/rules/reliability/no-broad-exception.ts
79
89
  var import_utils2 = require("@typescript-eslint/utils");
80
90
  var createRule2 = import_utils2.ESLintUtils.RuleCreator(
81
91
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
@@ -152,7 +162,7 @@ function containsInstanceofCheck(node, paramName) {
152
162
  return false;
153
163
  }
154
164
 
155
- // src/rules/error-handling/no-catch-log-rethrow.ts
165
+ // src/rules/reliability/no-catch-log-rethrow.ts
156
166
  var import_utils3 = require("@typescript-eslint/utils");
157
167
  var createRule3 = import_utils3.ESLintUtils.RuleCreator(
158
168
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
@@ -214,7 +224,7 @@ var noCatchLogRethrow = createRule3({
214
224
  }
215
225
  });
216
226
 
217
- // src/rules/error-handling/no-catch-without-use.ts
227
+ // src/rules/reliability/no-catch-without-use.ts
218
228
  var import_utils4 = require("@typescript-eslint/utils");
219
229
  var createRule4 = import_utils4.ESLintUtils.RuleCreator(
220
230
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
@@ -648,6 +658,7 @@ var noFloatingPromise = createRule6({
648
658
 
649
659
  // src/rules/async/no-await-in-loop.ts
650
660
  var import_utils7 = require("@typescript-eslint/utils");
661
+ var import_path = __toESM(require("path"));
651
662
  var createRule7 = import_utils7.ESLintUtils.RuleCreator(
652
663
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
653
664
  );
@@ -661,6 +672,7 @@ var LOOP_TYPES = /* @__PURE__ */ new Set([
661
672
  var SUPPRESSION_REGEX = /ai-guard-disable\s+no-await-in-loop\b/i;
662
673
  var RETRY_NAME_REGEX = /(retry|retries|attempt|attempts|fallback|tryagain|recovery)/i;
663
674
  var SEQUENTIAL_DEPENDENCY_NAME_REGEX = /(previous|prev|last|carry|accumulator|stateful)/i;
675
+ var SIMULATION_NAME_REGEX = /(simulat|animat|demo|visuali|step|scene|frame|render|tick|sequence|tutorial|lesson|walk)/i;
664
676
  var ERROR_CODE_HINTS = [
665
677
  "access-denied",
666
678
  "timeout",
@@ -675,6 +687,15 @@ var CONTROL_AWAIT_HINTS = [
675
687
  "ratelimit",
676
688
  "backoff"
677
689
  ];
690
+ var TIMER_FUNCTION_NAMES = /* @__PURE__ */ new Set([
691
+ "setinterval",
692
+ "settimeout",
693
+ "requestanimationframe",
694
+ "queuemicrotask",
695
+ "sleep",
696
+ "delay",
697
+ "wait"
698
+ ]);
678
699
  var MUTATION_METHOD_NAMES = /* @__PURE__ */ new Set([
679
700
  "push",
680
701
  "pop",
@@ -875,6 +896,9 @@ function analyzeIntent(loopNode) {
875
896
  if (SEQUENTIAL_DEPENDENCY_NAME_REGEX.test(name)) {
876
897
  hasSequentialDependency = true;
877
898
  }
899
+ if (SIMULATION_NAME_REGEX.test(name)) {
900
+ hasSequentialDependency = true;
901
+ }
878
902
  };
879
903
  walkNode(
880
904
  loopNode,
@@ -889,6 +913,9 @@ function analyzeIntent(loopNode) {
889
913
  if (CONTROL_AWAIT_HINTS.some((hint) => lower.includes(hint))) {
890
914
  hasControlAwait = true;
891
915
  }
916
+ if (TIMER_FUNCTION_NAMES.has(lower)) {
917
+ hasControlAwait = true;
918
+ }
892
919
  }
893
920
  }
894
921
  if (node.type === import_utils7.AST_NODE_TYPES.UpdateExpression) {
@@ -1025,16 +1052,43 @@ var noAwaitInLoop = createRule7({
1025
1052
  description: "Disallow independent `await` usage inside loops, while allowing intentional retry/fallback/sequential workflows. AI tools frequently generate accidental sequential awaits where Promise.all would be safer and faster. Includes a safe autofix for simple independent loops."
1026
1053
  },
1027
1054
  fixable: "code",
1028
- schema: [],
1055
+ schema: [
1056
+ {
1057
+ type: "object",
1058
+ properties: {
1059
+ allowPatterns: {
1060
+ type: "array",
1061
+ items: { type: "string" },
1062
+ description: "File glob patterns to skip (e.g., **/*.test.ts, **/simulation/**)"
1063
+ }
1064
+ },
1065
+ additionalProperties: false
1066
+ }
1067
+ ],
1029
1068
  messages: {
1030
- awaitInLoop: "Unexpected `await` inside a {{loopType}}. AI tools frequently generate sequential awaits in loops, causing O(n) latency. Consider collecting promises and using `Promise.all()` for parallel execution."
1069
+ awaitInLoop: "Sequential `await` inside a {{loopType}} \u2014 if these items are independent, consider `Promise.all()` for parallel execution. If sequential order matters, this may be intentional."
1031
1070
  }
1032
1071
  },
1033
- defaultOptions: [],
1034
- create(context) {
1072
+ defaultOptions: [{}],
1073
+ create(context, [options]) {
1035
1074
  if (hasFileSuppression(context.sourceCode)) {
1036
1075
  return {};
1037
1076
  }
1077
+ const filename = context.filename ?? context.getFilename?.() ?? "";
1078
+ const fileBasename = import_path.default.basename(filename).toLowerCase();
1079
+ const filePath = filename.toLowerCase();
1080
+ const isTestFile = fileBasename.includes(".test.") || fileBasename.includes(".spec.") || filePath.includes("/__tests__/") || filePath.includes("/test/") || filePath.includes("/tests/");
1081
+ const isSimulationFile = SIMULATION_NAME_REGEX.test(fileBasename) || filePath.includes("/simulation/") || filePath.includes("/demo/") || filePath.includes("/animation/") || filePath.includes("/fixtures/");
1082
+ if (isTestFile || isSimulationFile) {
1083
+ return {};
1084
+ }
1085
+ const { allowPatterns = [] } = options;
1086
+ for (const pattern of allowPatterns) {
1087
+ const cleaned = pattern.replace(/\*\*/g, "").replace(/\*/g, "").replace(/\//g, import_path.default.sep);
1088
+ if (cleaned && filePath.includes(cleaned.toLowerCase())) {
1089
+ return {};
1090
+ }
1091
+ }
1038
1092
  const loopIntentCache = /* @__PURE__ */ new WeakMap();
1039
1093
  const reportedLoops = /* @__PURE__ */ new WeakSet();
1040
1094
  const functionBoundary = [];
@@ -1095,9 +1149,36 @@ var noAwaitInLoop = createRule7({
1095
1149
 
1096
1150
  // src/rules/async/no-async-without-await.ts
1097
1151
  var import_utils8 = require("@typescript-eslint/utils");
1152
+ var import_path2 = __toESM(require("path"));
1098
1153
  var createRule8 = import_utils8.ESLintUtils.RuleCreator(
1099
1154
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
1100
1155
  );
1156
+ var FRAMEWORK_FILE_PATTERNS = [
1157
+ // Next.js App Router
1158
+ /[/\\]route\.(ts|js|tsx|jsx)$/,
1159
+ /[/\\]middleware\.(ts|js)$/,
1160
+ /[/\\]layout\.(tsx|jsx|ts|js)$/,
1161
+ /[/\\]page\.(tsx|jsx|ts|js)$/,
1162
+ /[/\\]loading\.(tsx|jsx|ts|js)$/,
1163
+ /[/\\]error\.(tsx|jsx|ts|js)$/,
1164
+ /[/\\]not-found\.(tsx|jsx|ts|js)$/,
1165
+ /[/\\]template\.(tsx|jsx|ts|js)$/,
1166
+ // Next.js Pages Router conventions
1167
+ /[/\\]_app\.(tsx|jsx)$/,
1168
+ /[/\\]_document\.(tsx|jsx)$/
1169
+ ];
1170
+ var HTTP_METHOD_EXPORTS = /* @__PURE__ */ new Set([
1171
+ "GET",
1172
+ "POST",
1173
+ "PUT",
1174
+ "PATCH",
1175
+ "DELETE",
1176
+ "HEAD",
1177
+ "OPTIONS"
1178
+ ]);
1179
+ var REACT_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
1180
+ var HANDLER_NAME_REGEX = /^(handle|on[A-Z])/;
1181
+ var MIDDLEWARE_NAME_REGEX = /^(use[A-Z]|middleware|interceptor|guard|beforeEach|afterEach)/;
1101
1182
  function containsAwaitExpression(node) {
1102
1183
  if (node.type === import_utils8.AST_NODE_TYPES.AwaitExpression) {
1103
1184
  return true;
@@ -1131,21 +1212,101 @@ function containsAwaitExpression(node) {
1131
1212
  }
1132
1213
  return false;
1133
1214
  }
1215
+ function isPassThroughWrapper(node) {
1216
+ if (node.body.type !== import_utils8.AST_NODE_TYPES.BlockStatement) {
1217
+ const body = node.body;
1218
+ if (body.type === import_utils8.AST_NODE_TYPES.CallExpression || body.type === import_utils8.AST_NODE_TYPES.MemberExpression) {
1219
+ return true;
1220
+ }
1221
+ return false;
1222
+ }
1223
+ if (node.body.body.length !== 1) return false;
1224
+ const only = node.body.body[0];
1225
+ if (only.type !== import_utils8.AST_NODE_TYPES.ReturnStatement) return false;
1226
+ if (!only.argument) return false;
1227
+ return only.argument.type === import_utils8.AST_NODE_TYPES.CallExpression;
1228
+ }
1229
+ function isFunctionExportedWithHttpMethodName(node) {
1230
+ if (node.type === import_utils8.AST_NODE_TYPES.FunctionDeclaration && node.id) {
1231
+ return HTTP_METHOD_EXPORTS.has(node.id.name);
1232
+ }
1233
+ if (node.parent?.type === import_utils8.AST_NODE_TYPES.VariableDeclarator) {
1234
+ const declarator = node.parent;
1235
+ if (declarator.id.type === import_utils8.AST_NODE_TYPES.Identifier && HTTP_METHOD_EXPORTS.has(declarator.id.name)) {
1236
+ return true;
1237
+ }
1238
+ }
1239
+ return false;
1240
+ }
1241
+ function getFunctionName(node) {
1242
+ if (node.type === import_utils8.AST_NODE_TYPES.FunctionDeclaration && node.id) {
1243
+ return node.id.name;
1244
+ }
1245
+ if (node.parent?.type === import_utils8.AST_NODE_TYPES.VariableDeclarator) {
1246
+ const declarator = node.parent;
1247
+ if (declarator.id.type === import_utils8.AST_NODE_TYPES.Identifier) {
1248
+ return declarator.id.name;
1249
+ }
1250
+ }
1251
+ if (node.parent?.type === import_utils8.AST_NODE_TYPES.Property || node.parent?.type === import_utils8.AST_NODE_TYPES.MethodDefinition) {
1252
+ const parent = node.parent;
1253
+ if ("key" in parent && parent.key.type === import_utils8.AST_NODE_TYPES.Identifier) {
1254
+ return parent.key.name;
1255
+ }
1256
+ }
1257
+ return null;
1258
+ }
1134
1259
  var noAsyncWithoutAwait = createRule8({
1135
1260
  name: "no-async-without-await",
1136
1261
  meta: {
1137
1262
  type: "suggestion",
1138
1263
  docs: {
1139
- description: "Disallow async functions that never use await. AI tools frequently add async by default, creating misleading signatures and unnecessary Promise wrappers. Includes a safe autofix for simple function bodies."
1264
+ description: "Disallow async functions that never use await. AI tools frequently add async by default, creating misleading signatures and unnecessary Promise wrappers. Framework-specific async conventions (Next.js route handlers, React event handlers) are excluded."
1140
1265
  },
1141
1266
  fixable: "code",
1142
- schema: [],
1267
+ schema: [
1268
+ {
1269
+ type: "object",
1270
+ properties: {
1271
+ allowedFunctionNames: {
1272
+ type: "array",
1273
+ items: { type: "string" },
1274
+ description: "Additional function names to skip (on top of HTTP methods)"
1275
+ },
1276
+ frameworkFiles: {
1277
+ type: "array",
1278
+ items: { type: "string" },
1279
+ description: "Additional file glob patterns where async-without-await is allowed"
1280
+ },
1281
+ ignorePassThroughWrappers: {
1282
+ type: "boolean",
1283
+ description: "Downgrade (not suppress) pass-through wrappers. Default: true."
1284
+ },
1285
+ ignoreHandlerFunctions: {
1286
+ type: "boolean",
1287
+ description: "Skip functions named handle* or on* in .tsx/.jsx files. Default: true."
1288
+ }
1289
+ },
1290
+ additionalProperties: false
1291
+ }
1292
+ ],
1143
1293
  messages: {
1144
- asyncWithoutAwait: "Async function does not contain `await`. AI tools frequently add `async` unnecessarily, which can mislead callers and mask intent. Remove `async` or add proper await logic."
1294
+ asyncWithoutAwait: "Async function does not contain `await`. AI tools frequently add `async` unnecessarily, which can mislead callers and mask intent. Remove `async` or add proper await logic.",
1295
+ asyncPassThrough: "Async pass-through wrapper \u2014 this may be intentional for Promise propagation. Consider whether `async` is needed here."
1145
1296
  }
1146
1297
  },
1147
- defaultOptions: [],
1148
- create(context) {
1298
+ defaultOptions: [{}],
1299
+ create(context, [options]) {
1300
+ const {
1301
+ allowedFunctionNames = [],
1302
+ ignorePassThroughWrappers = true,
1303
+ ignoreHandlerFunctions = true
1304
+ } = options;
1305
+ const skipNames = /* @__PURE__ */ new Set([...HTTP_METHOD_EXPORTS, ...allowedFunctionNames]);
1306
+ const filename = context.filename ?? context.getFilename?.() ?? "";
1307
+ const fileExt = import_path2.default.extname(filename).toLowerCase();
1308
+ const isFrameworkFile = FRAMEWORK_FILE_PATTERNS.some((pattern) => pattern.test(filename));
1309
+ const isReactFile = REACT_FILE_EXTENSIONS.has(fileExt);
1149
1310
  function buildSafeAutofix2(node) {
1150
1311
  const sourceCode = context.sourceCode;
1151
1312
  if (node.body.type !== import_utils8.AST_NODE_TYPES.BlockStatement) {
@@ -1170,25 +1331,51 @@ var noAsyncWithoutAwait = createRule8({
1170
1331
  if (!node.async) {
1171
1332
  return;
1172
1333
  }
1334
+ if (isFrameworkFile) {
1335
+ return;
1336
+ }
1337
+ if (isFunctionExportedWithHttpMethodName(node)) {
1338
+ return;
1339
+ }
1340
+ const funcName = getFunctionName(node);
1341
+ if (funcName && skipNames.has(funcName)) {
1342
+ return;
1343
+ }
1344
+ if (funcName && MIDDLEWARE_NAME_REGEX.test(funcName)) {
1345
+ return;
1346
+ }
1347
+ if (ignoreHandlerFunctions && isReactFile && funcName && HANDLER_NAME_REGEX.test(funcName)) {
1348
+ return;
1349
+ }
1350
+ const bodyHasAwait = node.body.type === import_utils8.AST_NODE_TYPES.BlockStatement ? containsAwaitExpression(node.body) : node.body.type === import_utils8.AST_NODE_TYPES.AwaitExpression;
1351
+ if (bodyHasAwait) {
1352
+ return;
1353
+ }
1354
+ if (ignorePassThroughWrappers && isPassThroughWrapper(node)) {
1355
+ context.report({
1356
+ node,
1357
+ messageId: "asyncPassThrough"
1358
+ // No autofix for pass-through — it's intentional
1359
+ });
1360
+ return;
1361
+ }
1173
1362
  if (node.body.type !== import_utils8.AST_NODE_TYPES.BlockStatement) {
1174
1363
  if (node.body.type !== import_utils8.AST_NODE_TYPES.AwaitExpression) {
1175
- const fix = buildSafeAutofix2(node);
1364
+ const fix2 = buildSafeAutofix2(node);
1176
1365
  context.report({
1177
1366
  node,
1178
1367
  messageId: "asyncWithoutAwait",
1179
- fix
1368
+ fix: fix2
1180
1369
  });
1181
1370
  }
1182
1371
  return;
1183
1372
  }
1184
- if (!containsAwaitExpression(node.body)) {
1185
- const fix = buildSafeAutofix2(node);
1186
- context.report({
1187
- node,
1188
- messageId: "asyncWithoutAwait",
1189
- fix
1190
- });
1191
- }
1373
+ const fix = buildSafeAutofix2(node);
1374
+ context.report({
1375
+ node,
1376
+ messageId: "asyncWithoutAwait",
1377
+ fix
1378
+ });
1192
1379
  }
1193
1380
  return {
1194
1381
  FunctionDeclaration: reportIfNeeded,
@@ -1464,10 +1651,20 @@ var noEvalDynamic = createRule11({
1464
1651
  messageId: "evalDynamic",
1465
1652
  data: { callee: `${node.callee.object.name}.eval()` }
1466
1653
  });
1654
+ return;
1655
+ }
1656
+ if (isFunctionConstructorCallee(node.callee)) {
1657
+ if (node.arguments.length === 0) return;
1658
+ const allLiteral = node.arguments.every(isLiteral);
1659
+ if (allLiteral) return;
1660
+ context.report({
1661
+ node,
1662
+ messageId: "newFunctionDynamic"
1663
+ });
1467
1664
  }
1468
1665
  },
1469
1666
  NewExpression(node) {
1470
- if (node.callee.type === import_utils11.AST_NODE_TYPES.Identifier && node.callee.name === "Function") {
1667
+ if (isFunctionConstructorCallee(node.callee)) {
1471
1668
  if (node.arguments.length === 0) return;
1472
1669
  const allLiteral = node.arguments.every(isLiteral);
1473
1670
  if (allLiteral) return;
@@ -1480,6 +1677,15 @@ var noEvalDynamic = createRule11({
1480
1677
  };
1481
1678
  }
1482
1679
  });
1680
+ function isFunctionConstructorCallee(callee) {
1681
+ if (callee.type === import_utils11.AST_NODE_TYPES.Identifier && callee.name === "Function") {
1682
+ return true;
1683
+ }
1684
+ if (callee.type === import_utils11.AST_NODE_TYPES.MemberExpression && callee.property.type === import_utils11.AST_NODE_TYPES.Identifier && callee.property.name === "Function" && callee.object.type === import_utils11.AST_NODE_TYPES.Identifier && (callee.object.name === "window" || callee.object.name === "globalThis")) {
1685
+ return true;
1686
+ }
1687
+ return false;
1688
+ }
1483
1689
  function isLiteral(node) {
1484
1690
  if (node.type === import_utils11.AST_NODE_TYPES.Literal) return true;
1485
1691
  if (node.type === import_utils11.AST_NODE_TYPES.TemplateLiteral && node.expressions.length === 0) {
@@ -1897,6 +2103,8 @@ var PUBLIC_ROUTE_PATTERNS = [
1897
2103
  // reset password
1898
2104
  /^\/webhook/,
1899
2105
  // webhooks
2106
+ /^\/callback/,
2107
+ // OAuth callback
1900
2108
  /^\/public/,
1901
2109
  // explicitly public
1902
2110
  /^\/assets/,
@@ -1907,8 +2115,12 @@ var PUBLIC_ROUTE_PATTERNS = [
1907
2115
  // favicon
1908
2116
  /^\/robots/,
1909
2117
  // robots.txt
1910
- /^\/sitemap/
2118
+ /^\/sitemap/,
1911
2119
  // sitemap
2120
+ /^\/api\/docs/,
2121
+ // API documentation (Swagger, etc.)
2122
+ /^\/docs/
2123
+ // documentation
1912
2124
  ];
1913
2125
  var requireAuthMiddleware = createRule14({
1914
2126
  name: "require-auth-middleware",
@@ -2005,6 +2217,13 @@ function isAuthMiddleware(node, authNames) {
2005
2217
  if (node.type === import_utils14.AST_NODE_TYPES.Identifier) {
2006
2218
  return authNames.has(node.name);
2007
2219
  }
2220
+ if (node.type === import_utils14.AST_NODE_TYPES.ObjectExpression) {
2221
+ for (const prop of node.properties) {
2222
+ if (prop.type === import_utils14.AST_NODE_TYPES.Property && prop.key.type === import_utils14.AST_NODE_TYPES.Identifier && (prop.key.name === "preHandler" || prop.key.name === "onRequest")) {
2223
+ if (isAuthMiddleware(prop.value, authNames)) return true;
2224
+ }
2225
+ }
2226
+ }
2008
2227
  if (node.type === import_utils14.AST_NODE_TYPES.CallExpression) {
2009
2228
  if (node.callee.type === import_utils14.AST_NODE_TYPES.Identifier) {
2010
2229
  return authNames.has(node.callee.name);
@@ -2090,29 +2309,29 @@ function getMemberPath(node) {
2090
2309
  }
2091
2310
  return [...objectPath, node.property.name];
2092
2311
  }
2093
- function hasPathPrefix(path, prefix) {
2094
- if (!path || path.length < prefix.length) {
2312
+ function hasPathPrefix(path3, prefix) {
2313
+ if (!path3 || path3.length < prefix.length) {
2095
2314
  return false;
2096
2315
  }
2097
2316
  for (let i = 0; i < prefix.length; i += 1) {
2098
- if (path[i] !== prefix[i]) {
2317
+ if (path3[i] !== prefix[i]) {
2099
2318
  return false;
2100
2319
  }
2101
2320
  }
2102
2321
  return true;
2103
2322
  }
2104
- function isLikelyResourceIdPath(path) {
2105
- if (!path || path.length < 3) {
2323
+ function isLikelyResourceIdPath(path3) {
2324
+ if (!path3 || path3.length < 3) {
2106
2325
  return false;
2107
2326
  }
2108
- if (!hasPathPrefix(path, ["req", "params"]) && !hasPathPrefix(path, ["req", "body"]) && !hasPathPrefix(path, ["req", "query"])) {
2327
+ if (!hasPathPrefix(path3, ["req", "params"]) && !hasPathPrefix(path3, ["req", "body"]) && !hasPathPrefix(path3, ["req", "query"])) {
2109
2328
  return false;
2110
2329
  }
2111
- const last = path[path.length - 1].toLowerCase();
2330
+ const last = path3[path3.length - 1].toLowerCase();
2112
2331
  return last === "id" || last.endsWith("id");
2113
2332
  }
2114
- function isReqUserPath(path) {
2115
- return hasPathPrefix(path, ["req", "user"]);
2333
+ function isReqUserPath(path3) {
2334
+ return hasPathPrefix(path3, ["req", "user"]);
2116
2335
  }
2117
2336
  function containsAuthorizationHelper(node) {
2118
2337
  if (node.type === import_utils15.AST_NODE_TYPES.CallExpression && (node.callee.type === import_utils15.AST_NODE_TYPES.Identifier && AUTHZ_HELPER_NAMES.includes(node.callee.name) || node.callee.type === import_utils15.AST_NODE_TYPES.MemberExpression && node.callee.property.type === import_utils15.AST_NODE_TYPES.Identifier && AUTHZ_HELPER_NAMES.includes(node.callee.property.name))) {
@@ -2151,8 +2370,8 @@ function collectBodySignals(node) {
2151
2370
  }
2152
2371
  }
2153
2372
  if (current.type === import_utils15.AST_NODE_TYPES.MemberExpression) {
2154
- const path = getMemberPath(current);
2155
- if (isLikelyResourceIdPath(path)) {
2373
+ const path3 = getMemberPath(current);
2374
+ if (isLikelyResourceIdPath(path3)) {
2156
2375
  hasResourceIdAccess = true;
2157
2376
  }
2158
2377
  }
@@ -2221,7 +2440,7 @@ var requireAuthzCheck = createRule15({
2221
2440
  }
2222
2441
  });
2223
2442
 
2224
- // src/rules/quality/no-console-in-handler.ts
2443
+ // src/rules/ai-patterns/no-console-in-handler.ts
2225
2444
  var import_utils16 = require("@typescript-eslint/utils");
2226
2445
  var createRule16 = import_utils16.ESLintUtils.RuleCreator(
2227
2446
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
@@ -2331,7 +2550,7 @@ var noConsoleInHandler = createRule16({
2331
2550
  }
2332
2551
  });
2333
2552
 
2334
- // src/rules/logic/no-duplicate-logic-block.ts
2553
+ // src/rules/ai-patterns/no-duplicate-logic-block.ts
2335
2554
  var import_utils17 = require("@typescript-eslint/utils");
2336
2555
  var createRule17 = import_utils17.ESLintUtils.RuleCreator(
2337
2556
  (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
@@ -2396,25 +2615,127 @@ var noDuplicateLogicBlock = createRule17({
2396
2615
  }
2397
2616
  });
2398
2617
 
2618
+ // src/rules/ai-patterns/no-dead-branch.ts
2619
+ var import_utils18 = require("@typescript-eslint/utils");
2620
+ var createRule18 = import_utils18.ESLintUtils.RuleCreator(
2621
+ (name) => `https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules/${name}.md`
2622
+ );
2623
+ function isBooleanLiteral(node, value) {
2624
+ return node.type === import_utils18.AST_NODE_TYPES.Literal && typeof node.value === "boolean" && node.value === value;
2625
+ }
2626
+ function isZeroLiteral(node) {
2627
+ return node.type === import_utils18.AST_NODE_TYPES.Literal && typeof node.value === "number" && node.value === 0;
2628
+ }
2629
+ function isEmptyStringLiteral(node) {
2630
+ return node.type === import_utils18.AST_NODE_TYPES.Literal && typeof node.value === "string" && node.value === "";
2631
+ }
2632
+ function isAlwaysTrue(node) {
2633
+ if (isBooleanLiteral(node, true)) return true;
2634
+ if (node.type === import_utils18.AST_NODE_TYPES.LogicalExpression && node.operator === "||") {
2635
+ const { left, right } = node;
2636
+ if (left.type === import_utils18.AST_NODE_TYPES.Identifier && right.type === import_utils18.AST_NODE_TYPES.UnaryExpression && right.operator === "!" && right.argument.type === import_utils18.AST_NODE_TYPES.Identifier && left.name === right.argument.name) {
2637
+ return true;
2638
+ }
2639
+ }
2640
+ if (node.type === import_utils18.AST_NODE_TYPES.BinaryExpression && (node.operator === "===" || node.operator === "==") && node.left.type === import_utils18.AST_NODE_TYPES.Identifier && node.right.type === import_utils18.AST_NODE_TYPES.Identifier && node.left.name === node.right.name) {
2641
+ return true;
2642
+ }
2643
+ return false;
2644
+ }
2645
+ function isAlwaysFalse(node) {
2646
+ if (isBooleanLiteral(node, false)) return true;
2647
+ if (isZeroLiteral(node)) return true;
2648
+ if (isEmptyStringLiteral(node)) return true;
2649
+ if (node.type === import_utils18.AST_NODE_TYPES.LogicalExpression && node.operator === "&&") {
2650
+ const { left, right } = node;
2651
+ if (left.type === import_utils18.AST_NODE_TYPES.Identifier && right.type === import_utils18.AST_NODE_TYPES.UnaryExpression && right.operator === "!" && right.argument.type === import_utils18.AST_NODE_TYPES.Identifier && left.name === right.argument.name) {
2652
+ return true;
2653
+ }
2654
+ }
2655
+ if (node.type === import_utils18.AST_NODE_TYPES.BinaryExpression && (node.operator === "!==" || node.operator === "!=") && node.left.type === import_utils18.AST_NODE_TYPES.Identifier && node.right.type === import_utils18.AST_NODE_TYPES.Identifier && node.left.name === node.right.name) {
2656
+ return true;
2657
+ }
2658
+ return false;
2659
+ }
2660
+ var noDeadBranch = createRule18({
2661
+ name: "no-dead-branch",
2662
+ meta: {
2663
+ type: "suggestion",
2664
+ docs: {
2665
+ description: "Disallow conditions that are statically always-true or always-false. AI tools frequently generate dead branches like `if (true)`, `if (false)`, `if (x && !x)`, or `x === x` \u2014 leftover scaffolding that clutters the code and hides logical errors."
2666
+ },
2667
+ fixable: void 0,
2668
+ schema: [],
2669
+ messages: {
2670
+ alwaysTrue: "This condition is always `true` \u2014 the branch is never skipped. AI tools often generate this as leftover scaffolding. Remove the condition or replace it with the intended logic.",
2671
+ alwaysFalse: "This condition is always `false` \u2014 this branch is dead and never executed. AI tools often generate this as leftover scaffolding. Remove the dead branch entirely.",
2672
+ whileFalse: "This `while (false)` loop never executes. AI tools sometimes generate this as a placeholder. Remove it or replace with the intended loop condition."
2673
+ }
2674
+ },
2675
+ defaultOptions: [],
2676
+ create(context) {
2677
+ function checkCondition(testNode, reportNode, isBranchStatement) {
2678
+ if (!testNode) return;
2679
+ if (isAlwaysFalse(testNode)) {
2680
+ context.report({
2681
+ node: reportNode,
2682
+ messageId: isBranchStatement ? "alwaysFalse" : "whileFalse"
2683
+ });
2684
+ return;
2685
+ }
2686
+ if (isAlwaysTrue(testNode)) {
2687
+ if (reportNode.type === import_utils18.AST_NODE_TYPES.WhileStatement) return;
2688
+ context.report({
2689
+ node: reportNode,
2690
+ messageId: "alwaysTrue"
2691
+ });
2692
+ }
2693
+ }
2694
+ return {
2695
+ IfStatement(node) {
2696
+ checkCondition(node.test, node, true);
2697
+ },
2698
+ WhileStatement(node) {
2699
+ if (isAlwaysFalse(node.test)) {
2700
+ context.report({ node, messageId: "whileFalse" });
2701
+ }
2702
+ },
2703
+ DoWhileStatement(node) {
2704
+ if (isAlwaysFalse(node.test)) {
2705
+ context.report({ node, messageId: "whileFalse" });
2706
+ }
2707
+ },
2708
+ ConditionalExpression(node) {
2709
+ checkCondition(node.test, node, true);
2710
+ }
2711
+ };
2712
+ }
2713
+ });
2714
+
2399
2715
  // src/rules/index.ts
2400
2716
  var allRules = {
2717
+ // Reliability
2401
2718
  "no-empty-catch": noEmptyCatch,
2402
2719
  "no-broad-exception": noBroadException,
2403
2720
  "no-catch-log-rethrow": noCatchLogRethrow,
2404
2721
  "no-catch-without-use": noCatchWithoutUse,
2722
+ // Async
2405
2723
  "no-async-array-callback": noAsyncArrayCallback,
2406
2724
  "no-floating-promise": noFloatingPromise,
2407
2725
  "no-await-in-loop": noAwaitInLoop,
2408
2726
  "no-async-without-await": noAsyncWithoutAwait,
2409
2727
  "no-redundant-await": noRedundantAwait,
2728
+ // Security
2410
2729
  "no-hardcoded-secret": noHardcodedSecret,
2411
2730
  "no-eval-dynamic": noEvalDynamic,
2412
2731
  "no-sql-string-concat": noSqlStringConcat,
2413
2732
  "no-unsafe-deserialize": noUnsafeDeserialize,
2414
2733
  "require-auth-middleware": requireAuthMiddleware,
2415
2734
  "require-authz-check": requireAuthzCheck,
2735
+ // AI Patterns
2416
2736
  "no-console-in-handler": noConsoleInHandler,
2417
- "no-duplicate-logic-block": noDuplicateLogicBlock
2737
+ "no-duplicate-logic-block": noDuplicateLogicBlock,
2738
+ "no-dead-branch": noDeadBranch
2418
2739
  };
2419
2740
 
2420
2741
  // src/configs/recommended.ts
@@ -2424,27 +2745,35 @@ var recommended = {
2424
2745
  // Adoption-first default:
2425
2746
  // Keep only high-confidence, high-impact rules at error so first run is actionable,
2426
2747
  // not overwhelming. Context-sensitive rules are warn/off by design.
2427
- // Critical (error): low-noise, high-impact correctness/security failures.
2748
+ // ── Reliability ────────────────────────────────────────────────────────────
2749
+ // Critical (error): low-noise, high-impact correctness failures.
2428
2750
  "ai-guard/no-empty-catch": "error",
2429
- "ai-guard/no-floating-promise": "error",
2430
- "ai-guard/no-hardcoded-secret": "error",
2431
- "ai-guard/no-eval-dynamic": "error",
2432
- // Important but noisier/context-dependent (warn): useful guidance without blocking.
2751
+ // Important but context-dependent (warn).
2433
2752
  "ai-guard/no-broad-exception": "warn",
2434
- "ai-guard/require-auth-middleware": "warn",
2753
+ // Off in recommended — too noisy in mixed codebases.
2754
+ "ai-guard/no-catch-without-use": "off",
2755
+ "ai-guard/no-catch-log-rethrow": "off",
2756
+ // ── Async ──────────────────────────────────────────────────────────────────
2757
+ "ai-guard/no-floating-promise": "error",
2435
2758
  "ai-guard/no-await-in-loop": "warn",
2436
2759
  "ai-guard/no-async-without-await": "warn",
2437
- "ai-guard/no-sql-string-concat": "warn",
2438
- // Kept as warn in recommended to reduce false positives in mixed codebases.
2439
2760
  "ai-guard/no-async-array-callback": "warn",
2440
- // Optional/contextual (off): available for teams via strict/custom configs.
2441
- // Intentional for trust and gradual adoption.
2442
- "ai-guard/no-console-in-handler": "off",
2761
+ // Off in recommended — reduces noise in common patterns.
2443
2762
  "ai-guard/no-redundant-await": "off",
2444
- "ai-guard/no-catch-without-use": "off",
2763
+ // ── Security ───────────────────────────────────────────────────────────────
2764
+ // Critical (error): direct security vulnerabilities.
2765
+ "ai-guard/no-hardcoded-secret": "error",
2766
+ "ai-guard/no-eval-dynamic": "error",
2767
+ // Context-sensitive (warn): useful but require project context.
2768
+ "ai-guard/no-sql-string-concat": "warn",
2445
2769
  "ai-guard/no-unsafe-deserialize": "warn",
2446
- "ai-guard/no-catch-log-rethrow": "off",
2770
+ "ai-guard/require-auth-middleware": "warn",
2447
2771
  "ai-guard/require-authz-check": "warn",
2772
+ // ── AI Patterns ────────────────────────────────────────────────────────────
2773
+ // Warn: useful signal, but may fire in intentional dead-code removal flows.
2774
+ "ai-guard/no-dead-branch": "warn",
2775
+ // Off in recommended — contextual rules for teams opting into stricter checks.
2776
+ "ai-guard/no-console-in-handler": "off",
2448
2777
  "ai-guard/no-duplicate-logic-block": "off"
2449
2778
  }
2450
2779
  };
@@ -2474,9 +2803,10 @@ var strict = {
2474
2803
  "ai-guard/no-unsafe-deserialize": "error",
2475
2804
  "ai-guard/require-auth-middleware": "error",
2476
2805
  "ai-guard/require-authz-check": "error",
2477
- // Quality - all at error
2806
+ // AI Patterns - all at error
2478
2807
  "ai-guard/no-console-in-handler": "error",
2479
- "ai-guard/no-duplicate-logic-block": "error"
2808
+ "ai-guard/no-duplicate-logic-block": "error",
2809
+ "ai-guard/no-dead-branch": "error"
2480
2810
  }
2481
2811
  };
2482
2812
  var strict_default = strict;
@@ -2501,7 +2831,7 @@ var security_default = security;
2501
2831
  var plugin = {
2502
2832
  meta: {
2503
2833
  name: "eslint-plugin-ai-guard",
2504
- version: "1.1.13"
2834
+ version: "1.2.0"
2505
2835
  },
2506
2836
  rules: allRules,
2507
2837
  configs: {