@fedify/lint 2.4.0-dev.2027 → 2.4.0-dev.2035

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.
@@ -1,7 +1,7 @@
1
1
  import { always, every, head, isEmpty, isObject, negate, pipe, pipeLazy, prop, some, toArray, unless, when } from "@fxts/core";
2
2
  //#region deno.json
3
3
  var name = "@fedify/lint";
4
- var version = "2.4.0-dev.2027+fcf88bfc";
4
+ var version = "2.4.0-dev.2035+d1f8f8e2";
5
5
  //#endregion
6
6
  //#region src/lib/const.ts
7
7
  /**
@@ -141,6 +141,7 @@ const RULE_IDS = {
141
141
  actorUploadMediaPropertyMismatch: "actor-upload-media-property-mismatch",
142
142
  collectionFilteringNotImplemented: "collection-filtering-not-implemented",
143
143
  outboxListenerDeliveryRequired: "outbox-listener-delivery-required",
144
+ outboxListenerDeliveryNotAwaited: "outbox-listener-delivery-not-awaited",
144
145
  mediaUploaderObjectUriRequired: "media-uploader-object-uri-required",
145
146
  mediaUploaderAuthorizationRequired: "media-uploader-authorization-required"
146
147
  };
@@ -462,7 +463,7 @@ function createRequiredRuleEslint(config) {
462
463
  };
463
464
  }
464
465
  createRequiredRuleDeno(properties.assertionMethod);
465
- const eslint$26 = createRequiredRuleEslint(properties.assertionMethod);
466
+ const eslint$27 = createRequiredRuleEslint(properties.assertionMethod);
466
467
  //#endregion
467
468
  //#region src/lib/mismatch.ts
468
469
  const isIdentifierWithName = (name) => (node) => allOf(isNodeType("Identifier"), isNodeName(name))(node);
@@ -532,49 +533,49 @@ const createMismatchRuleEslint = (config) => ({
532
533
  }))
533
534
  });
534
535
  createMismatchRuleDeno(properties.featured);
535
- const eslint$25 = createMismatchRuleEslint(properties.featured);
536
+ const eslint$26 = createMismatchRuleEslint(properties.featured);
536
537
  createRequiredRuleDeno(properties.featured);
537
- const eslint$24 = createRequiredRuleEslint(properties.featured);
538
+ const eslint$25 = createRequiredRuleEslint(properties.featured);
538
539
  createMismatchRuleDeno(properties.featuredTags);
539
- const eslint$23 = createMismatchRuleEslint(properties.featuredTags);
540
+ const eslint$24 = createMismatchRuleEslint(properties.featuredTags);
540
541
  createRequiredRuleDeno(properties.featuredTags);
541
- const eslint$22 = createRequiredRuleEslint(properties.featuredTags);
542
+ const eslint$23 = createRequiredRuleEslint(properties.featuredTags);
542
543
  createMismatchRuleDeno(properties.followers);
543
- const eslint$21 = createMismatchRuleEslint(properties.followers);
544
+ const eslint$22 = createMismatchRuleEslint(properties.followers);
544
545
  createRequiredRuleDeno(properties.followers);
545
- const eslint$20 = createRequiredRuleEslint(properties.followers);
546
+ const eslint$21 = createRequiredRuleEslint(properties.followers);
546
547
  createMismatchRuleDeno(properties.following);
547
- const eslint$19 = createMismatchRuleEslint(properties.following);
548
+ const eslint$20 = createMismatchRuleEslint(properties.following);
548
549
  createRequiredRuleDeno(properties.following);
549
- const eslint$18 = createRequiredRuleEslint(properties.following);
550
+ const eslint$19 = createRequiredRuleEslint(properties.following);
550
551
  createMismatchRuleDeno(properties.id);
551
- const eslint$17 = createMismatchRuleEslint(properties.id);
552
+ const eslint$18 = createMismatchRuleEslint(properties.id);
552
553
  createRequiredRuleDeno(properties.id);
553
- const eslint$16 = createRequiredRuleEslint(properties.id);
554
+ const eslint$17 = createRequiredRuleEslint(properties.id);
554
555
  createMismatchRuleDeno(properties.inbox);
555
- const eslint$15 = createMismatchRuleEslint(properties.inbox);
556
+ const eslint$16 = createMismatchRuleEslint(properties.inbox);
556
557
  createRequiredRuleDeno(properties.inbox);
557
- const eslint$14 = createRequiredRuleEslint(properties.inbox);
558
+ const eslint$15 = createRequiredRuleEslint(properties.inbox);
558
559
  createMismatchRuleDeno(properties.liked);
559
- const eslint$13 = createMismatchRuleEslint(properties.liked);
560
+ const eslint$14 = createMismatchRuleEslint(properties.liked);
560
561
  createRequiredRuleDeno(properties.liked);
561
- const eslint$12 = createRequiredRuleEslint(properties.liked);
562
+ const eslint$13 = createRequiredRuleEslint(properties.liked);
562
563
  createMismatchRuleDeno(properties.outbox);
563
- const eslint$11 = createMismatchRuleEslint(properties.outbox);
564
+ const eslint$12 = createMismatchRuleEslint(properties.outbox);
564
565
  createRequiredRuleDeno(properties.outbox);
565
- const eslint$10 = createRequiredRuleEslint(properties.outbox);
566
+ const eslint$11 = createRequiredRuleEslint(properties.outbox);
566
567
  createRequiredRuleDeno(properties.preferredUsername);
567
- const eslint$9 = createRequiredRuleEslint(properties.preferredUsername);
568
+ const eslint$10 = createRequiredRuleEslint(properties.preferredUsername);
568
569
  createRequiredRuleDeno(properties.publicKey);
569
- const eslint$8 = createRequiredRuleEslint(properties.publicKey);
570
+ const eslint$9 = createRequiredRuleEslint(properties.publicKey);
570
571
  createMismatchRuleDeno(properties.sharedInbox);
571
- const eslint$7 = createMismatchRuleEslint(properties.sharedInbox);
572
+ const eslint$8 = createMismatchRuleEslint(properties.sharedInbox);
572
573
  createRequiredRuleDeno(properties.sharedInbox);
573
- const eslint$6 = createRequiredRuleEslint(properties.sharedInbox);
574
+ const eslint$7 = createRequiredRuleEslint(properties.sharedInbox);
574
575
  createMismatchRuleDeno(properties.uploadMedia);
575
- const eslint$5 = createMismatchRuleEslint(properties.uploadMedia);
576
+ const eslint$6 = createMismatchRuleEslint(properties.uploadMedia);
576
577
  createRequiredRuleDeno(properties.uploadMedia);
577
- const eslint$4 = createRequiredRuleEslint(properties.uploadMedia);
578
+ const eslint$5 = createRequiredRuleEslint(properties.uploadMedia);
578
579
  //#endregion
579
580
  //#region src/rules/collection-filtering-not-implemented.ts
580
581
  /**
@@ -593,7 +594,7 @@ const isFollowersDispatcherCall = (node) => "callee" in node && node.callee && n
593
594
  * CollectionDispatcher signature: (context, identifier, cursor, filter?) => ...
594
595
  */
595
596
  const hasFilterParameter = hasMinParams(4);
596
- const eslint$3 = {
597
+ const eslint$4 = {
597
598
  meta: {
598
599
  type: "suggestion",
599
600
  docs: { description: "Ensure followers dispatcher implements filtering" },
@@ -619,7 +620,7 @@ const eslint$3 = {
619
620
  };
620
621
  //#endregion
621
622
  //#region src/rules/media-uploader-authorization-required.ts
622
- const MESSAGE$2 = "setMediaUploader() should be protected with .authorize(); without it the endpoint accepts uploads from anyone who can reach the URL. Add .authorize() unless a public upload endpoint is intended.";
623
+ const MESSAGE$3 = "setMediaUploader() should be protected with .authorize(); without it the endpoint accepts uploads from anyone who can reach the URL. Add .authorize() unless a public upload endpoint is intended.";
623
624
  function getParent(node) {
624
625
  if (node == null || typeof node !== "object") return null;
625
626
  const parent = node.parent;
@@ -637,7 +638,7 @@ function isDirectlyAuthorized(callNode) {
637
638
  const call = getParent(member);
638
639
  return call != null && call.type === "CallExpression" && call.callee === member;
639
640
  }
640
- function createRule$2(buildReport) {
641
+ function createRule$3(buildReport) {
641
642
  return (context) => {
642
643
  const federationTracker = trackFederationVariables();
643
644
  const unchained = [];
@@ -675,21 +676,21 @@ function createRule$2(buildReport) {
675
676
  };
676
677
  };
677
678
  }
678
- const eslint$2 = {
679
+ const eslint$3 = {
679
680
  meta: {
680
681
  type: "suggestion",
681
682
  docs: { description: "Warn when setMediaUploader() is registered without an authorize() hook" },
682
683
  schema: [],
683
684
  messages: { required: "{{ message }}" }
684
685
  },
685
- create: createRule$2({
686
+ create: createRule$3({
686
687
  messageId: "required",
687
- data: { message: MESSAGE$2 }
688
+ data: { message: MESSAGE$3 }
688
689
  })
689
690
  };
690
691
  //#endregion
691
692
  //#region src/rules/media-uploader-object-uri-required.ts
692
- const MESSAGE$1 = "setMediaUploader() callbacks should return a value derived from ctx.getObjectUri(): either return ctx.getObjectUri(...) directly, or an object whose id is ctx.getObjectUri(...).";
693
+ const MESSAGE$2 = "setMediaUploader() callbacks should return a value derived from ctx.getObjectUri(): either return ctx.getObjectUri(...) directly, or an object whose id is ctx.getObjectUri(...).";
693
694
  const GETTER_NAME = "getObjectUri";
694
695
  const getMemberPropertyName$1 = (node) => {
695
696
  if (node.type !== "MemberExpression") return null;
@@ -963,7 +964,7 @@ function callbackDerivesFromGetObjectUri(callback) {
963
964
  };
964
965
  return returnedExpressions.every((expr) => returnDerivesFromGetObjectUri(expr, ctx, /* @__PURE__ */ new Set()));
965
966
  }
966
- function createRule$1(buildReport) {
967
+ function createRule$2(buildReport) {
967
968
  return (context) => {
968
969
  const federationTracker = trackFederationVariables();
969
970
  const bindings = /* @__PURE__ */ new Map();
@@ -997,21 +998,21 @@ function createRule$1(buildReport) {
997
998
  };
998
999
  };
999
1000
  }
1000
- const eslint$1 = {
1001
+ const eslint$2 = {
1001
1002
  meta: {
1002
1003
  type: "suggestion",
1003
1004
  docs: { description: "Warn when a setMediaUploader() callback does not derive its return value from ctx.getObjectUri()" },
1004
1005
  schema: [],
1005
1006
  messages: { required: "{{ message }}" }
1006
1007
  },
1007
- create: createRule$1({
1008
+ create: createRule$2({
1008
1009
  messageId: "required",
1009
- data: { message: MESSAGE$1 }
1010
+ data: { message: MESSAGE$2 }
1010
1011
  })
1011
1012
  };
1012
1013
  //#endregion
1013
- //#region src/rules/outbox-listener-delivery-required.ts
1014
- const MESSAGE = "Outbox listeners should deliver posted activities explicitly with ctx.sendActivity() or ctx.forwardActivity().";
1014
+ //#region src/lib/outbox-listener.ts
1015
+ const DELIVERY_METHOD_NAMES = /* @__PURE__ */ new Set(["sendActivity", "forwardActivity"]);
1015
1016
  const isChainedFromOutboxListeners = (expr, federationTracker) => {
1016
1017
  if (expr.type !== "CallExpression") return false;
1017
1018
  if (!hasMemberExpressionCallee(expr) || !hasIdentifierProperty(expr)) return false;
@@ -1020,13 +1021,6 @@ const isChainedFromOutboxListeners = (expr, federationTracker) => {
1020
1021
  if (methodName === "authorize" || methodName === "onError" || methodName === "on") return isChainedFromOutboxListeners(expr.callee.object, federationTracker);
1021
1022
  return false;
1022
1023
  };
1023
- const DELIVERY_METHOD_NAMES = /* @__PURE__ */ new Set(["sendActivity", "forwardActivity"]);
1024
- const FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
1025
- "FunctionDeclaration",
1026
- "FunctionExpression",
1027
- "ArrowFunctionExpression"
1028
- ]);
1029
- const isFunctionLikeNode = (node) => FUNCTION_NODE_TYPES.has(node.type);
1030
1024
  const getMemberPropertyName = (expr) => {
1031
1025
  if (expr.type !== "MemberExpression") return null;
1032
1026
  const property = expr.property;
@@ -1039,133 +1033,6 @@ function unwrapContextParam(node) {
1039
1033
  while (current?.type === "AssignmentPattern") current = current.left;
1040
1034
  return current;
1041
1035
  }
1042
- function escapeRegExp(value) {
1043
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
1044
- }
1045
- function stripCommentsAndStrings(code) {
1046
- let result = "";
1047
- let index = 0;
1048
- const skipQuotedString = (quote) => {
1049
- const start = index;
1050
- index += 1;
1051
- while (index < code.length) {
1052
- const char = code[index];
1053
- if (char === "\\") {
1054
- index += 2;
1055
- continue;
1056
- }
1057
- index += 1;
1058
- if (char === quote) break;
1059
- }
1060
- const literal = code.slice(start, index);
1061
- const value = literal.slice(1, -1);
1062
- result += DELIVERY_METHOD_NAMES.has(value) ? literal : `${quote}${quote}`;
1063
- };
1064
- const stripTemplateLiteral = () => {
1065
- const start = index;
1066
- index += 1;
1067
- let raw = "";
1068
- let hasExpression = false;
1069
- while (index < code.length) {
1070
- const char = code[index];
1071
- if (char === "\\") {
1072
- raw += char;
1073
- raw += code[index + 1] ?? "";
1074
- index += 2;
1075
- continue;
1076
- }
1077
- if (char === "`") {
1078
- index += 1;
1079
- if (!hasExpression && DELIVERY_METHOD_NAMES.has(raw)) result += code.slice(start, index);
1080
- else result += "``";
1081
- return;
1082
- }
1083
- if (char === "$" && code[index + 1] === "{") {
1084
- hasExpression = true;
1085
- result += "`${";
1086
- index += 2;
1087
- let depth = 1;
1088
- while (index < code.length && depth > 0) {
1089
- const exprChar = code[index];
1090
- const next = code[index + 1];
1091
- if (exprChar === "'" || exprChar === "\"") {
1092
- skipQuotedString(exprChar);
1093
- continue;
1094
- }
1095
- if (exprChar === "`") {
1096
- stripTemplateLiteral();
1097
- continue;
1098
- }
1099
- if (exprChar === "/" && next === "*") {
1100
- index += 2;
1101
- while (index < code.length) {
1102
- if (code[index] === "*" && code[index + 1] === "/") {
1103
- index += 2;
1104
- break;
1105
- }
1106
- index += 1;
1107
- }
1108
- continue;
1109
- }
1110
- if (exprChar === "/" && next === "/") {
1111
- index += 2;
1112
- while (index < code.length && code[index] !== "\n") index += 1;
1113
- continue;
1114
- }
1115
- result += exprChar;
1116
- index += 1;
1117
- if (exprChar === "{") depth += 1;
1118
- else if (exprChar === "}") depth -= 1;
1119
- }
1120
- continue;
1121
- }
1122
- raw += char;
1123
- index += 1;
1124
- }
1125
- result += "``";
1126
- };
1127
- while (index < code.length) {
1128
- const char = code[index];
1129
- const next = code[index + 1];
1130
- if (char === "/" && next === "*") {
1131
- index += 2;
1132
- while (index < code.length) {
1133
- if (code[index] === "*" && code[index + 1] === "/") {
1134
- index += 2;
1135
- break;
1136
- }
1137
- index += 1;
1138
- }
1139
- continue;
1140
- }
1141
- if (char === "/" && next === "/") {
1142
- index += 2;
1143
- while (index < code.length && code[index] !== "\n") index += 1;
1144
- continue;
1145
- }
1146
- if (char === "'" || char === "\"") {
1147
- skipQuotedString(char);
1148
- continue;
1149
- }
1150
- if (char === "`") {
1151
- stripTemplateLiteral();
1152
- continue;
1153
- }
1154
- result += char;
1155
- index += 1;
1156
- }
1157
- return result;
1158
- }
1159
- function getDeliveryAliasName(node) {
1160
- if (node.type === "Identifier") return node.name;
1161
- if (node.type === "AssignmentPattern" && node.left.type === "Identifier") return node.left.name;
1162
- return null;
1163
- }
1164
- function buildContextExpressionPattern(contextName) {
1165
- const name = escapeRegExp(contextName);
1166
- const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
1167
- return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
1168
- }
1169
1036
  /**
1170
1037
  * Resolves an expression to the function it refers to: a direct function
1171
1038
  * literal, a local variable bound to one, or a property of a local object
@@ -1198,6 +1065,49 @@ const resolveFunctionBinding = (expr, bindings, seen = /* @__PURE__ */ new Set()
1198
1065
  }
1199
1066
  return null;
1200
1067
  };
1068
+ /**
1069
+ * Builds the visitor an outbox-listener rule registers: it follows
1070
+ * `federation.setOutboxListeners(...).on(Type, listener)` chains through the
1071
+ * file and, once the whole file has been seen, hands each listener, resolved
1072
+ * to its function, to `onListener`. The listener may be an inline function or
1073
+ * a name bound to one elsewhere in the file.
1074
+ */
1075
+ function createOutboxListenerVisitor(onListener) {
1076
+ const federationTracker = trackFederationVariables();
1077
+ const bindings = /* @__PURE__ */ new Map();
1078
+ const pendingCalls = [];
1079
+ const inspectCall = (node) => {
1080
+ if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
1081
+ if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
1082
+ const listener = node.arguments[1];
1083
+ const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveFunctionBinding(listener, bindings) : null;
1084
+ if (resolvedListener == null) return;
1085
+ onListener(resolvedListener);
1086
+ };
1087
+ return {
1088
+ VariableDeclarator(node) {
1089
+ federationTracker.VariableDeclarator(node);
1090
+ if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
1091
+ },
1092
+ FunctionDeclaration(node) {
1093
+ if (node.id != null) bindings.set(node.id.name, node);
1094
+ },
1095
+ CallExpression(node) {
1096
+ pendingCalls.push(node);
1097
+ },
1098
+ "Program:exit"() {
1099
+ for (const node of pendingCalls) inspectCall(node);
1100
+ }
1101
+ };
1102
+ }
1103
+ //#endregion
1104
+ //#region src/lib/reachability.ts
1105
+ const FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
1106
+ "FunctionDeclaration",
1107
+ "FunctionExpression",
1108
+ "ArrowFunctionExpression"
1109
+ ]);
1110
+ const isFunctionLikeNode = (node) => FUNCTION_NODE_TYPES.has(node.type);
1201
1111
  const isStaticallyFalsy = (test) => test.type === "Literal" && !test.value;
1202
1112
  const isStaticallyTruthy = (test) => test.type === "Literal" && Boolean(test.value);
1203
1113
  /**
@@ -1297,10 +1207,10 @@ function collectReachableStatements(node, out) {
1297
1207
  * `deliver`), and the target of an assignment, which writes to a name
1298
1208
  * instead of reading it.
1299
1209
  */
1300
- function collectReferencedNames(node, out) {
1210
+ function collectReferencedNames(node, out, crossFunctions = false) {
1301
1211
  if (node == null || typeof node !== "object") return;
1302
1212
  if (Array.isArray(node)) {
1303
- for (const item of node) collectReferencedNames(item, out);
1213
+ for (const item of node) collectReferencedNames(item, out, crossFunctions);
1304
1214
  return;
1305
1215
  }
1306
1216
  if (!isNode(node)) return;
@@ -1310,37 +1220,40 @@ function collectReferencedNames(node, out) {
1310
1220
  return;
1311
1221
  }
1312
1222
  if (n.type === "MemberExpression" && !n.computed) {
1313
- collectReferencedNames(n.object, out);
1223
+ collectReferencedNames(n.object, out, crossFunctions);
1314
1224
  return;
1315
1225
  }
1316
1226
  if (n.type === "Property" && !n.computed) {
1317
- collectReferencedNames(n.value, out);
1227
+ collectReferencedNames(n.value, out, crossFunctions);
1318
1228
  return;
1319
1229
  }
1320
1230
  if (n.type === "VariableDeclarator") {
1321
- if (n.init != null) collectReferencedNames(n.init, out);
1231
+ if (n.init != null) collectReferencedNames(n.init, out, crossFunctions);
1322
1232
  return;
1323
1233
  }
1324
1234
  if (n.type === "ClassDeclaration" || n.type === "ClassExpression") {
1325
- collectReferencedNames(n.superClass, out);
1326
- collectReferencedNames(n.body, out);
1235
+ collectReferencedNames(n.superClass, out, crossFunctions);
1236
+ collectReferencedNames(n.body, out, crossFunctions);
1327
1237
  return;
1328
1238
  }
1329
1239
  if ((n.type === "MethodDefinition" || n.type === "PropertyDefinition") && !n.computed) {
1330
- collectReferencedNames(n.value, out);
1240
+ collectReferencedNames(n.value, out, crossFunctions);
1331
1241
  return;
1332
1242
  }
1333
1243
  if (n.type === "AssignmentExpression" && n.operator === "=") {
1334
1244
  if (getAssignmentTargetName(n.left) != null) {
1335
- collectReferencedNames(n.right, out);
1245
+ collectReferencedNames(n.right, out, crossFunctions);
1336
1246
  return;
1337
1247
  }
1338
1248
  }
1339
- if (isFunctionLikeNode(n)) return;
1249
+ if (isFunctionLikeNode(n)) {
1250
+ if (crossFunctions) collectReferencedNames(n.body, out, crossFunctions);
1251
+ return;
1252
+ }
1340
1253
  const record = n;
1341
1254
  for (const key in record) {
1342
1255
  if (key === "parent") continue;
1343
- collectReferencedNames(record[key], out);
1256
+ collectReferencedNames(record[key], out, crossFunctions);
1344
1257
  }
1345
1258
  }
1346
1259
  /**
@@ -1480,19 +1393,20 @@ function collectNestedFunctions(node, out) {
1480
1393
  }
1481
1394
  }
1482
1395
  /**
1483
- * Computes the full set of function nodes that are actually reachable from
1484
- * `root`: `root` itself feeds a worklist, and each function it (or a
1485
- * function already on the worklist) uses from *its own* reachable
1486
- * statements -- never from a dead branch or some other not-yet-reached
1487
- * function's body -- gets queued in turn. `outerFunctionsByName` is layered
1488
- * fresh for each scope, so a name bound at an inner scope shadows a
1489
- * same-named one further out instead of overwriting it globally, and a
1490
- * dead branch that merely mentions a name never queues what it holds.
1396
+ * Walks every scope that is actually reachable from `root`, calling `visit`
1397
+ * for each, and returns the set of function nodes found to be used: `root`
1398
+ * itself feeds a worklist, and each function it (or a function already on
1399
+ * the worklist) uses from *its own* reachable statements -- never from a
1400
+ * dead branch or some other not-yet-reached function's body -- gets queued in
1401
+ * turn. `outerFunctionsByName` is layered fresh for each scope, so a name
1402
+ * bound at an inner scope shadows a same-named one further out instead of
1403
+ * overwriting it globally, and a dead branch that merely mentions a name
1404
+ * never queues what it holds.
1491
1405
  */
1492
- function computeUsedFunctions(root) {
1406
+ function walkUsedScopes(root, visit) {
1493
1407
  const used = /* @__PURE__ */ new Set();
1494
1408
  const visited = /* @__PURE__ */ new Set();
1495
- const processScope = (scopeRoot, outerFunctionsByName) => {
1409
+ const processScope = (scopeRoot, scopeFn, outerFunctionsByName) => {
1496
1410
  if (visited.has(scopeRoot)) return;
1497
1411
  visited.add(scopeRoot);
1498
1412
  const statements = [];
@@ -1501,6 +1415,11 @@ function computeUsedFunctions(root) {
1501
1415
  for (const statement of statements) collectFunctionsByName(statement, functionsHere);
1502
1416
  const functionsByName = new Map(outerFunctionsByName);
1503
1417
  for (const [name, functions] of functionsHere) functionsByName.set(name, functions);
1418
+ visit({
1419
+ fn: scopeFn,
1420
+ statements,
1421
+ functionsByName
1422
+ });
1504
1423
  const referencedNames = /* @__PURE__ */ new Set();
1505
1424
  for (const statement of statements) collectReferencedNames(statement, referencedNames);
1506
1425
  const reached = /* @__PURE__ */ new Set();
@@ -1515,15 +1434,22 @@ function computeUsedFunctions(root) {
1515
1434
  collectNestedFunctions(statement, nested);
1516
1435
  for (const fn of nested) if (!held.has(fn)) reached.add(fn);
1517
1436
  }
1518
- for (const fn of reached) {
1519
- used.add(fn);
1520
- processScope(fn.body, functionsByName);
1437
+ for (const reachedFn of reached) {
1438
+ used.add(reachedFn);
1439
+ processScope(reachedFn.body, reachedFn, functionsByName);
1521
1440
  }
1522
1441
  };
1523
- processScope(root, /* @__PURE__ */ new Map());
1442
+ processScope(root, null, /* @__PURE__ */ new Map());
1524
1443
  return used;
1525
1444
  }
1526
1445
  /**
1446
+ * The set of function nodes that are actually reachable from `root`. See
1447
+ * `walkUsedScopes`.
1448
+ */
1449
+ function computeUsedFunctions(root) {
1450
+ return walkUsedScopes(root, () => {});
1451
+ }
1452
+ /**
1527
1453
  * A node's `[start, end)` character offsets into the whole source file.
1528
1454
  * Both engines always populate this -- ESLint forces it on regardless of
1529
1455
  * parser options, and Deno.lint exposes it the same way as every other
@@ -1533,6 +1459,493 @@ function computeUsedFunctions(root) {
1533
1459
  function getRange(node) {
1534
1460
  return node.range;
1535
1461
  }
1462
+ //#endregion
1463
+ //#region src/rules/outbox-listener-delivery-not-awaited.ts
1464
+ const MESSAGE$1 = "Delivery is not awaited, so the activity may be lost once the handler returns (for example on Cloudflare Workers). Await it, return it, or pass it to waitUntil().";
1465
+ const HANDLED = {
1466
+ fate: "handled",
1467
+ owner: null
1468
+ };
1469
+ const DROPPED = {
1470
+ fate: "dropped",
1471
+ owner: null
1472
+ };
1473
+ /** Wrappers that pass a value through unchanged. */
1474
+ const TRANSPARENT_WRAPPERS = /* @__PURE__ */ new Set([
1475
+ "ChainExpression",
1476
+ "TSAsExpression",
1477
+ "TSNonNullExpression",
1478
+ "TSSatisfiesExpression",
1479
+ "TSTypeAssertion",
1480
+ "ParenthesizedExpression"
1481
+ ]);
1482
+ /**
1483
+ * `Promise` methods that turn promises into one promise. `race()` and
1484
+ * `any()` settle as soon as one input does, which is accepted as a
1485
+ * deliberate choice to stop waiting, like `void`. A `race()` or `any()`
1486
+ * that is itself dropped is still reported.
1487
+ */
1488
+ const PROMISE_COMBINATORS = /* @__PURE__ */ new Set([
1489
+ "all",
1490
+ "allSettled",
1491
+ "race",
1492
+ "any"
1493
+ ]);
1494
+ /** Methods on a promise that return a new promise carrying the chain on. */
1495
+ const PROMISE_CHAIN_METHODS = /* @__PURE__ */ new Set([
1496
+ "then",
1497
+ "catch",
1498
+ "finally"
1499
+ ]);
1500
+ const get = (node, key) => node == null ? void 0 : node[key];
1501
+ const asNode = (value) => isNode(value) ? value : null;
1502
+ const parentOf = (node) => asNode(get(node, "parent"));
1503
+ function unwrap(node) {
1504
+ let current = node;
1505
+ while (TRANSPARENT_WRAPPERS.has(current.type)) {
1506
+ const inner = asNode(get(current, "expression"));
1507
+ if (inner == null) break;
1508
+ current = inner;
1509
+ }
1510
+ return current;
1511
+ }
1512
+ /** The name a member expression accesses, when it is spelled out plainly. */
1513
+ function memberName(node) {
1514
+ if (node.type !== "MemberExpression") return null;
1515
+ const property = asNode(get(node, "property"));
1516
+ if (property == null) return null;
1517
+ if (get(node, "computed") !== true) return property.type === "Identifier" ? property.name : null;
1518
+ if (property.type === "Literal" && typeof property.value === "string") return property.value;
1519
+ if (property.type === "TemplateLiteral") {
1520
+ const quasis = get(property, "quasis");
1521
+ if (get(property, "expressions").length === 0 && quasis.length === 1) return quasis[0].cooked ?? quasis[0].value?.cooked ?? null;
1522
+ }
1523
+ return null;
1524
+ }
1525
+ /** Visits every node in `node`, stopping at a nested function unless asked to cross it. */
1526
+ function visitAll(node, visitor, crossFunctions = true) {
1527
+ if (node == null || typeof node !== "object") return;
1528
+ if (Array.isArray(node)) {
1529
+ for (const item of node) visitAll(item, visitor, crossFunctions);
1530
+ return;
1531
+ }
1532
+ if (!isNode(node)) return;
1533
+ if (!crossFunctions && isFunctionLikeNode(node)) return;
1534
+ visitor(node);
1535
+ const record = node;
1536
+ for (const key in record) if (key !== "parent") visitAll(record[key], visitor, crossFunctions);
1537
+ }
1538
+ /** Collects the calls in `node` that run in the same function, not in one nested in it. */
1539
+ function collectCalls(node, out) {
1540
+ visitAll(node, (n) => {
1541
+ if (n.type === "CallExpression") out.push(n);
1542
+ }, false);
1543
+ }
1544
+ function enclosingFunction(node) {
1545
+ for (let current = parentOf(node); current != null;) {
1546
+ if (isFunctionLikeNode(current)) return current;
1547
+ current = parentOf(current);
1548
+ }
1549
+ return null;
1550
+ }
1551
+ /** The names an object pattern binds the delivery methods to. */
1552
+ function deliveryAliasesOf(pattern, out) {
1553
+ if (pattern.type !== "ObjectPattern") return;
1554
+ for (const prop of get(pattern, "properties")) {
1555
+ if (prop.type !== "Property") continue;
1556
+ const key = asNode(get(prop, "key"));
1557
+ const keyName = key?.type === "Identifier" ? key.name : key?.type === "Literal" && typeof key.value === "string" ? key.value : null;
1558
+ if (keyName == null || !DELIVERY_METHOD_NAMES.has(keyName)) continue;
1559
+ const value = asNode(get(prop, "value"));
1560
+ if (value?.type === "Identifier") out.add(value.name);
1561
+ else if (value?.type === "AssignmentPattern") {
1562
+ const left = asNode(get(value, "left"));
1563
+ if (left?.type === "Identifier") out.add(left.name);
1564
+ }
1565
+ }
1566
+ }
1567
+ /**
1568
+ * Works out how the listener refers to the delivery methods: through its
1569
+ * context parameter (`ctx.sendActivity`), or through a name bound to one of
1570
+ * them, either in the parameter (`{ sendActivity }`) or in code that can run
1571
+ * (`const { sendActivity } = ctx`, `const send = ctx.sendActivity`). A name
1572
+ * bound in a helper nothing uses, or in a dead branch, does not count.
1573
+ */
1574
+ function findDeliveryTargets(listener, scopes) {
1575
+ const aliases = /* @__PURE__ */ new Set();
1576
+ const contextParam = unwrapContextParam(listener.params[0]);
1577
+ const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
1578
+ if (contextParam != null) deliveryAliasesOf(contextParam, aliases);
1579
+ if (contextName != null) for (const scope of scopes) for (const statement of scope.statements) visitAll(statement, (node) => {
1580
+ if (node.type !== "VariableDeclarator") return;
1581
+ const id = asNode(get(node, "id"));
1582
+ const init = asNode(get(node, "init"));
1583
+ if (id == null || init == null) return;
1584
+ const source = unwrap(init);
1585
+ if (source.type === "Identifier" && source.name === contextName) {
1586
+ deliveryAliasesOf(id, aliases);
1587
+ return;
1588
+ }
1589
+ if (id.type !== "Identifier" || source.type !== "MemberExpression") return;
1590
+ const object = unwrap(asNode(get(source, "object")) ?? source);
1591
+ const name = memberName(source);
1592
+ if (object.type === "Identifier" && object.name === contextName && name != null && DELIVERY_METHOD_NAMES.has(name)) aliases.add(id.name);
1593
+ }, false);
1594
+ return {
1595
+ contextName,
1596
+ aliases
1597
+ };
1598
+ }
1599
+ /**
1600
+ * Where the value a callback returns goes, judged by the call that receives
1601
+ * the callback.
1602
+ */
1603
+ function callbackResultOutcome(call, analysis) {
1604
+ const name = memberName(unwrap(asNode(get(call, "callee")) ?? call));
1605
+ if (name === "forEach") return DROPPED;
1606
+ if (name === "map" || name === "flatMap") return fateOf(call, analysis, "promises");
1607
+ if (name != null && PROMISE_CHAIN_METHODS.has(name)) return fateOf(call, analysis, "promise");
1608
+ return HANDLED;
1609
+ }
1610
+ /** Where the promise a function returns goes, given who calls the function. */
1611
+ function returnedOutcome(fn, analysis) {
1612
+ if (fn == null) return HANDLED;
1613
+ if (fn === analysis.listener) return {
1614
+ fate: "returned",
1615
+ owner: fn
1616
+ };
1617
+ const parent = parentOf(fn);
1618
+ if (parent?.type === "CallExpression") {
1619
+ if (get(parent, "callee") === fn) return fateOf(parent, analysis, "promise");
1620
+ if (get(parent, "arguments").includes(fn)) return callbackResultOutcome(parent, analysis);
1621
+ }
1622
+ return {
1623
+ fate: "returned",
1624
+ owner: fn
1625
+ };
1626
+ }
1627
+ /**
1628
+ * Where an array of promises, or an object holding one, goes when it is
1629
+ * returned. The listener's caller does not wait for what is inside it. Any
1630
+ * other function hands it to its own callers, who may well pass it to
1631
+ * `Promise.all()` or read the promise back out of it.
1632
+ */
1633
+ function returnedContainerOutcome(fn, analysis) {
1634
+ return fn === analysis.listener ? DROPPED : HANDLED;
1635
+ }
1636
+ /** Follows the value of `expression` up through its parents to where it ends up. */
1637
+ function fateOf(expression, analysis, shape = "promise") {
1638
+ let current = expression;
1639
+ let currentShape = shape;
1640
+ for (;;) {
1641
+ const parent = parentOf(current);
1642
+ if (parent == null) return HANDLED;
1643
+ if (TRANSPARENT_WRAPPERS.has(parent.type)) {
1644
+ current = parent;
1645
+ continue;
1646
+ }
1647
+ switch (parent.type) {
1648
+ case "AwaitExpression": return currentShape === "promise" ? {
1649
+ fate: "awaited",
1650
+ owner: enclosingFunction(parent)
1651
+ } : DROPPED;
1652
+ case "ReturnStatement": return currentShape === "promise" ? returnedOutcome(enclosingFunction(parent), analysis) : returnedContainerOutcome(enclosingFunction(parent), analysis);
1653
+ case "ArrowFunctionExpression":
1654
+ if (get(parent, "body") !== current) return HANDLED;
1655
+ return currentShape === "promise" ? returnedOutcome(parent, analysis) : returnedContainerOutcome(parent, analysis);
1656
+ case "ExpressionStatement": return DROPPED;
1657
+ case "UnaryExpression": return get(parent, "operator") === "void" ? HANDLED : DROPPED;
1658
+ case "SequenceExpression": {
1659
+ const expressions = get(parent, "expressions");
1660
+ if (expressions[expressions.length - 1] !== current) return DROPPED;
1661
+ current = parent;
1662
+ continue;
1663
+ }
1664
+ case "ConditionalExpression":
1665
+ if (get(parent, "test") === current) return DROPPED;
1666
+ current = parent;
1667
+ continue;
1668
+ case "IfStatement":
1669
+ case "WhileStatement":
1670
+ case "DoWhileStatement":
1671
+ case "ForStatement": return DROPPED;
1672
+ case "LogicalExpression":
1673
+ case "Property":
1674
+ current = parent;
1675
+ continue;
1676
+ case "ObjectExpression":
1677
+ if (currentShape === "promise") currentShape = "object";
1678
+ current = parent;
1679
+ continue;
1680
+ case "ArrayExpression":
1681
+ if (currentShape !== "promise") return HANDLED;
1682
+ currentShape = "promises";
1683
+ current = parent;
1684
+ continue;
1685
+ case "SpreadElement": {
1686
+ const array = parentOf(parent);
1687
+ if (currentShape !== "promises" || array?.type !== "ArrayExpression") return HANDLED;
1688
+ current = array;
1689
+ continue;
1690
+ }
1691
+ case "MemberExpression": {
1692
+ if (get(parent, "object") !== current) return HANDLED;
1693
+ const call = parentOf(parent);
1694
+ const name = memberName(parent);
1695
+ if (currentShape === "promise" && call?.type === "CallExpression" && get(call, "callee") === parent && name != null && PROMISE_CHAIN_METHODS.has(name)) {
1696
+ current = call;
1697
+ continue;
1698
+ }
1699
+ return HANDLED;
1700
+ }
1701
+ case "CallExpression": {
1702
+ if (get(parent, "callee") === current) return HANDLED;
1703
+ const callee = unwrap(asNode(get(parent, "callee")) ?? parent);
1704
+ const name = memberName(callee);
1705
+ if (name === "waitUntil") return HANDLED;
1706
+ const object = callee.type === "MemberExpression" ? unwrap(asNode(get(callee, "object")) ?? callee) : null;
1707
+ if (object?.type === "Identifier" && object.name === "Promise" && name != null && PROMISE_COMBINATORS.has(name)) {
1708
+ current = parent;
1709
+ currentShape = "promise";
1710
+ continue;
1711
+ }
1712
+ return HANDLED;
1713
+ }
1714
+ case "VariableDeclarator": {
1715
+ const id = asNode(get(parent, "id"));
1716
+ if (get(parent, "init") !== current || id?.type !== "Identifier") return HANDLED;
1717
+ return analysis.mentioned.has(id.name) ? HANDLED : DROPPED;
1718
+ }
1719
+ case "AssignmentExpression": {
1720
+ if (get(parent, "right") !== current) return HANDLED;
1721
+ const name = getAssignmentTargetName(asNode(get(parent, "left")) ?? parent);
1722
+ if (name == null) return HANDLED;
1723
+ return analysis.mentioned.has(name) ? HANDLED : DROPPED;
1724
+ }
1725
+ default: return HANDLED;
1726
+ }
1727
+ }
1728
+ }
1729
+ /** Reports every place in `listener` where a delivery promise is dropped. */
1730
+ function checkListener(listener, report) {
1731
+ const scopes = [];
1732
+ walkUsedScopes(listener.body, (scope) => scopes.push(scope));
1733
+ const targets = findDeliveryTargets(listener, scopes);
1734
+ if (targets.contextName == null && targets.aliases.size === 0) return;
1735
+ const isDeliveryCall = (call) => {
1736
+ const callee = unwrap(call.callee);
1737
+ if (callee.type === "MemberExpression") {
1738
+ const name = memberName(callee);
1739
+ if (name == null || !DELIVERY_METHOD_NAMES.has(name)) return false;
1740
+ const object = unwrap(asNode(get(callee, "object")) ?? callee);
1741
+ return object.type === "Identifier" && object.name === targets.contextName;
1742
+ }
1743
+ return callee.type === "Identifier" && targets.aliases.has(callee.name);
1744
+ };
1745
+ const mentioned = /* @__PURE__ */ new Set();
1746
+ collectReferencedNames(listener.body, mentioned, true);
1747
+ const analysis = {
1748
+ listener,
1749
+ mentioned
1750
+ };
1751
+ const callsByScope = scopes.map((scope) => {
1752
+ const calls = [];
1753
+ for (const statement of scope.statements) collectCalls(statement, calls);
1754
+ return calls;
1755
+ });
1756
+ const carrying = /* @__PURE__ */ new Map();
1757
+ const carriesDelivery = (call, scope) => {
1758
+ if (isDeliveryCall(call)) return true;
1759
+ const callee = unwrap(call.callee);
1760
+ if (callee.type !== "Identifier") return false;
1761
+ return scope.functionsByName.get(callee.name)?.some((helper) => carrying.has(helper)) ?? false;
1762
+ };
1763
+ const noteCarrying = ({ fate, owner }) => {
1764
+ if (owner == null || owner === listener) return false;
1765
+ if (fate !== "awaited" && fate !== "returned") return false;
1766
+ const known = carrying.get(owner);
1767
+ if (known === "awaited" || known === fate) return false;
1768
+ carrying.set(owner, fate);
1769
+ return true;
1770
+ };
1771
+ for (let changed = true; changed;) {
1772
+ changed = false;
1773
+ scopes.forEach((scope, index) => {
1774
+ for (const call of callsByScope[index]) if (carriesDelivery(call, scope) && noteCarrying(fateOf(call, analysis))) changed = true;
1775
+ });
1776
+ }
1777
+ const reported = /* @__PURE__ */ new Set();
1778
+ const flag = (node) => {
1779
+ if (reported.has(node)) return;
1780
+ reported.add(node);
1781
+ report(node);
1782
+ };
1783
+ scopes.forEach((scope, index) => {
1784
+ for (const call of callsByScope[index]) {
1785
+ if (!carriesDelivery(call, scope)) continue;
1786
+ if (fateOf(call, analysis).fate === "dropped") flag(call);
1787
+ }
1788
+ });
1789
+ for (const [fn, kind] of carrying) if (kind === "awaited" && returnedOutcome(fn, analysis).fate === "dropped") flag(fn);
1790
+ scopes.forEach((scope, index) => {
1791
+ for (const call of callsByScope[index]) for (const arg of call.arguments) {
1792
+ if (arg.type !== "Identifier") continue;
1793
+ const held = scope.functionsByName.get(arg.name);
1794
+ if (held == null || !held.some((fn) => carrying.has(fn))) continue;
1795
+ if (callbackResultOutcome(call, analysis).fate === "dropped") flag(arg);
1796
+ }
1797
+ });
1798
+ }
1799
+ function createRule$1(buildReport) {
1800
+ return (context) => createOutboxListenerVisitor((listener) => checkListener(listener, (node) => {
1801
+ context.report({
1802
+ node,
1803
+ ...buildReport
1804
+ });
1805
+ }));
1806
+ }
1807
+ const eslint$1 = {
1808
+ meta: {
1809
+ type: "suggestion",
1810
+ docs: { description: "Warn when an outbox listener delivers an activity without awaiting it" },
1811
+ schema: [],
1812
+ messages: { notAwaited: "{{ message }}" }
1813
+ },
1814
+ create: createRule$1({
1815
+ messageId: "notAwaited",
1816
+ data: { message: MESSAGE$1 }
1817
+ })
1818
+ };
1819
+ //#endregion
1820
+ //#region src/rules/outbox-listener-delivery-required.ts
1821
+ const MESSAGE = "Outbox listeners should deliver posted activities explicitly with ctx.sendActivity() or ctx.forwardActivity().";
1822
+ function escapeRegExp(value) {
1823
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
1824
+ }
1825
+ function stripCommentsAndStrings(code) {
1826
+ let result = "";
1827
+ let index = 0;
1828
+ const skipQuotedString = (quote) => {
1829
+ const start = index;
1830
+ index += 1;
1831
+ while (index < code.length) {
1832
+ const char = code[index];
1833
+ if (char === "\\") {
1834
+ index += 2;
1835
+ continue;
1836
+ }
1837
+ index += 1;
1838
+ if (char === quote) break;
1839
+ }
1840
+ const literal = code.slice(start, index);
1841
+ const value = literal.slice(1, -1);
1842
+ result += DELIVERY_METHOD_NAMES.has(value) ? literal : `${quote}${quote}`;
1843
+ };
1844
+ const stripTemplateLiteral = () => {
1845
+ const start = index;
1846
+ index += 1;
1847
+ let raw = "";
1848
+ let hasExpression = false;
1849
+ while (index < code.length) {
1850
+ const char = code[index];
1851
+ if (char === "\\") {
1852
+ raw += char;
1853
+ raw += code[index + 1] ?? "";
1854
+ index += 2;
1855
+ continue;
1856
+ }
1857
+ if (char === "`") {
1858
+ index += 1;
1859
+ if (!hasExpression && DELIVERY_METHOD_NAMES.has(raw)) result += code.slice(start, index);
1860
+ else result += "``";
1861
+ return;
1862
+ }
1863
+ if (char === "$" && code[index + 1] === "{") {
1864
+ hasExpression = true;
1865
+ result += "`${";
1866
+ index += 2;
1867
+ let depth = 1;
1868
+ while (index < code.length && depth > 0) {
1869
+ const exprChar = code[index];
1870
+ const next = code[index + 1];
1871
+ if (exprChar === "'" || exprChar === "\"") {
1872
+ skipQuotedString(exprChar);
1873
+ continue;
1874
+ }
1875
+ if (exprChar === "`") {
1876
+ stripTemplateLiteral();
1877
+ continue;
1878
+ }
1879
+ if (exprChar === "/" && next === "*") {
1880
+ index += 2;
1881
+ while (index < code.length) {
1882
+ if (code[index] === "*" && code[index + 1] === "/") {
1883
+ index += 2;
1884
+ break;
1885
+ }
1886
+ index += 1;
1887
+ }
1888
+ continue;
1889
+ }
1890
+ if (exprChar === "/" && next === "/") {
1891
+ index += 2;
1892
+ while (index < code.length && code[index] !== "\n") index += 1;
1893
+ continue;
1894
+ }
1895
+ result += exprChar;
1896
+ index += 1;
1897
+ if (exprChar === "{") depth += 1;
1898
+ else if (exprChar === "}") depth -= 1;
1899
+ }
1900
+ continue;
1901
+ }
1902
+ raw += char;
1903
+ index += 1;
1904
+ }
1905
+ result += "``";
1906
+ };
1907
+ while (index < code.length) {
1908
+ const char = code[index];
1909
+ const next = code[index + 1];
1910
+ if (char === "/" && next === "*") {
1911
+ index += 2;
1912
+ while (index < code.length) {
1913
+ if (code[index] === "*" && code[index + 1] === "/") {
1914
+ index += 2;
1915
+ break;
1916
+ }
1917
+ index += 1;
1918
+ }
1919
+ continue;
1920
+ }
1921
+ if (char === "/" && next === "/") {
1922
+ index += 2;
1923
+ while (index < code.length && code[index] !== "\n") index += 1;
1924
+ continue;
1925
+ }
1926
+ if (char === "'" || char === "\"") {
1927
+ skipQuotedString(char);
1928
+ continue;
1929
+ }
1930
+ if (char === "`") {
1931
+ stripTemplateLiteral();
1932
+ continue;
1933
+ }
1934
+ result += char;
1935
+ index += 1;
1936
+ }
1937
+ return result;
1938
+ }
1939
+ function getDeliveryAliasName(node) {
1940
+ if (node.type === "Identifier") return node.name;
1941
+ if (node.type === "AssignmentPattern" && node.left.type === "Identifier") return node.left.name;
1942
+ return null;
1943
+ }
1944
+ function buildContextExpressionPattern(contextName) {
1945
+ const name = escapeRegExp(contextName);
1946
+ const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
1947
+ return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
1948
+ }
1536
1949
  /**
1537
1950
  * Builds the source text to scan for a delivery call: the reachable
1538
1951
  * statements of `root`, with every nested function literal either folded
@@ -1566,7 +1979,7 @@ function collectDeliveryScanCode(sourceCode, root, used, visited) {
1566
1979
  for (const fn of byDescendingStart) {
1567
1980
  const [fnStart, fnEnd] = getRange(fn);
1568
1981
  const replacement = used.has(fn) ? collectDeliveryScanCode(sourceCode, fn.body, used, visited) : "";
1569
- result = result.slice(0, fnStart - statementStart) + (replacement.length > 0 ? replacement : "()=>{}") + result.slice(fnEnd - statementStart);
1982
+ result = result.slice(0, fnStart - statementStart) + `\n${replacement.length > 0 ? replacement : "()=>{}"}\n` + result.slice(fnEnd - statementStart);
1570
1983
  }
1571
1984
  return result;
1572
1985
  }).join("\n");
@@ -1603,37 +2016,14 @@ const listenerCallsDeliveryMethod = (sourceCode, listener) => {
1603
2016
  };
1604
2017
  function createRule(buildReport) {
1605
2018
  return (context) => {
1606
- const federationTracker = trackFederationVariables();
1607
- const bindings = /* @__PURE__ */ new Map();
1608
- const pendingCalls = [];
1609
2019
  const sourceCode = context.sourceCode;
1610
- const inspectCall = (node) => {
1611
- if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
1612
- if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
1613
- const listener = node.arguments[1];
1614
- const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveFunctionBinding(listener, bindings) : null;
1615
- if (resolvedListener == null) return;
1616
- if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
2020
+ return createOutboxListenerVisitor((listener) => {
2021
+ if (listenerCallsDeliveryMethod(sourceCode, listener)) return;
1617
2022
  context.report({
1618
- node: resolvedListener,
2023
+ node: listener,
1619
2024
  ...buildReport
1620
2025
  });
1621
- };
1622
- return {
1623
- VariableDeclarator(node) {
1624
- federationTracker.VariableDeclarator(node);
1625
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
1626
- },
1627
- FunctionDeclaration(node) {
1628
- if (node.id != null) bindings.set(node.id.name, node);
1629
- },
1630
- CallExpression(node) {
1631
- pendingCalls.push(node);
1632
- },
1633
- "Program:exit"() {
1634
- for (const node of pendingCalls) inspectCall(node);
1635
- }
1636
- };
2026
+ });
1637
2027
  };
1638
2028
  }
1639
2029
  const eslint = {
@@ -1649,4 +2039,4 @@ const eslint = {
1649
2039
  })
1650
2040
  };
1651
2041
  //#endregion
1652
- export { version as A, eslint$22 as C, eslint$26 as D, eslint$25 as E, RULE_IDS as O, eslint$21 as S, eslint$24 as T, eslint$16 as _, eslint$4 as a, eslint$19 as b, eslint$7 as c, eslint$10 as d, eslint$11 as f, eslint$15 as g, eslint$14 as h, eslint$3 as i, name as k, eslint$8 as l, eslint$13 as m, eslint$1 as n, eslint$5 as o, eslint$12 as p, eslint$2 as r, eslint$6 as s, eslint as t, eslint$9 as u, eslint$17 as v, eslint$23 as w, eslint$20 as x, eslint$18 as y };
2042
+ export { name as A, eslint$22 as C, eslint$26 as D, eslint$25 as E, eslint$27 as O, eslint$21 as S, eslint$24 as T, eslint$16 as _, eslint$4 as a, eslint$19 as b, eslint$7 as c, eslint$10 as d, eslint$11 as f, eslint$15 as g, eslint$14 as h, eslint$3 as i, version as j, RULE_IDS as k, eslint$8 as l, eslint$13 as m, eslint$1 as n, eslint$5 as o, eslint$12 as p, eslint$2 as r, eslint$6 as s, eslint as t, eslint$9 as u, eslint$17 as v, eslint$23 as w, eslint$20 as x, eslint$18 as y };