@fedify/lint 2.3.9-dev.2048 → 2.3.10

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/deno.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@fedify/lint",
3
- "version": "2.3.9-dev.2048+c88fcc8c",
3
+ "version": "2.3.10",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
7
7
  "./oxlint": "./src/oxlint.ts"
8
8
  },
9
9
  "imports": {
10
+ "@fedify/fixture": "jsr:@fedify/fixture@^2.2.14",
10
11
  "eslint": "npm:eslint@^9.0.0",
11
12
  "estree": "npm:@types/estree@^1.0.8",
12
13
  "oxlint": "npm:oxlint@^1.66.0"
package/dist/index.cjs CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
- const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-C1Ho3dCQ.cjs");
27
+ const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-CwToC306.cjs");
28
28
  let _fxts_core = require("@fxts/core");
29
29
  let _typescript_eslint_parser = require("@typescript-eslint/parser");
30
30
  _typescript_eslint_parser = __toESM(_typescript_eslint_parser, 1);
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as RULE_IDS, S as eslint$19, T as version, _ as eslint$5, a as eslint$18, b as eslint$10, c as eslint$8, d as eslint$15, f as eslint$1, g as eslint$4, h as eslint$3, i as eslint$17, l as eslint$9, m as eslint$2, n as eslint$20, o as eslint$6, p as eslint, r as eslint$16, s as eslint$7, t as eslint$21, u as eslint$14, v as eslint$12, w as name, x as eslint$11, y as eslint$13 } from "./outbox-listener-delivery-required-CxugrN84.js";
1
+ import { C as RULE_IDS, S as eslint$19, T as version, _ as eslint$5, a as eslint$18, b as eslint$10, c as eslint$8, d as eslint$15, f as eslint$1, g as eslint$4, h as eslint$3, i as eslint$17, l as eslint$9, m as eslint$2, n as eslint$20, o as eslint$6, p as eslint, r as eslint$16, s as eslint$7, t as eslint$21, u as eslint$14, v as eslint$12, w as name, x as eslint$11, y as eslint$13 } from "./outbox-listener-delivery-required-BNgI_uik.js";
2
2
  import { fromEntries, keys, map, pipe } from "@fxts/core";
3
3
  import parser from "@typescript-eslint/parser";
4
4
  //#region src/index.ts
@@ -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.3.9-dev.2048+c88fcc8c";
4
+ var version = "2.3.10";
5
5
  //#endregion
6
6
  //#region src/lib/const.ts
7
7
  /**
@@ -595,7 +595,7 @@ const DELIVERY_METHOD_NAMES = new Set(["sendActivity", "forwardActivity"]);
595
595
  const getMemberPropertyName = (expr) => {
596
596
  if (expr.type !== "MemberExpression") return null;
597
597
  const property = expr.property;
598
- if (property.type === "Identifier") return property.name;
598
+ if (property.type === "Identifier" && !expr.computed) return property.name;
599
599
  if (property.type === "Literal" && typeof property.value === "string") return property.value;
600
600
  return null;
601
601
  };
@@ -731,35 +731,211 @@ function buildContextExpressionPattern(contextName) {
731
731
  const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
732
732
  return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
733
733
  }
734
- const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
735
- if (isFunction(expr)) return expr;
736
- if (expr.type === "Identifier") {
737
- if (seen.has(expr.name)) return null;
738
- seen.add(expr.name);
739
- const binding = bindings.get(expr.name);
740
- if (binding == null || !isNode(binding)) return null;
741
- if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
742
- if (binding.type === "Identifier") return resolveListenerReference(binding, bindings, seen);
743
- return null;
734
+ const getParent = (node) => node.parent ?? null;
735
+ const isFunctionLike = (node) => node.type === "FunctionDeclaration" || isFunction(node);
736
+ function bindPattern(node, bindings) {
737
+ switch (node.type) {
738
+ case "Identifier":
739
+ bindings.set(node.name, { value: null });
740
+ break;
741
+ case "AssignmentPattern":
742
+ bindPattern(node.left, bindings);
743
+ break;
744
+ case "RestElement":
745
+ bindPattern(node.argument, bindings);
746
+ break;
747
+ case "ObjectPattern":
748
+ for (const property of node.properties) bindPattern(property.type === "Property" ? property.value : property, bindings);
749
+ break;
750
+ case "ArrayPattern":
751
+ for (const element of node.elements) if (element != null) bindPattern(element, bindings);
752
+ break;
744
753
  }
745
- if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
746
- const binding = bindings.get(expr.object.name);
747
- if (binding == null || !isNode(binding) || binding.type !== "ObjectExpression") return null;
748
- const propertyName = getMemberPropertyName(expr);
749
- if (propertyName == null) return null;
750
- for (const prop of binding.properties) {
751
- if (!isNode(prop) || prop.type !== "Property") continue;
752
- if ((prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null) !== propertyName || !isNode(prop.value)) continue;
753
- const value = prop.value;
754
- if (isFunction(value) || value.type === "FunctionDeclaration") return value;
754
+ }
755
+ function createBindingIndex() {
756
+ const scopes = /* @__PURE__ */ new Map();
757
+ const scopeBindings = (scope) => {
758
+ let bindings = scopes.get(scope);
759
+ if (bindings == null) {
760
+ bindings = /* @__PURE__ */ new Map();
761
+ scopes.set(scope, bindings);
762
+ if (isFunctionLike(scope)) {
763
+ if (scope.type === "FunctionExpression" && scope.id != null) bindings.set(scope.id.name, { value: scope });
764
+ for (const param of scope.params) bindPattern(param, bindings);
765
+ }
766
+ if (scope.type === "ClassExpression" && scope.id != null) bindings.set(scope.id.name, { value: null });
767
+ if (scope.type === "CatchClause" && scope.param != null) bindPattern(scope.param, bindings);
768
+ }
769
+ return bindings;
770
+ };
771
+ const enclosingScope = (node, functionScope = false) => {
772
+ let scope = getParent(node);
773
+ while (scope.type !== "Program" && scope.type !== "StaticBlock" && scope.type !== "TSModuleBlock" && !isFunctionLike(scope) && (functionScope || ![
774
+ "BlockStatement",
775
+ "ForStatement",
776
+ "ForInStatement",
777
+ "ForOfStatement",
778
+ "SwitchStatement",
779
+ "CatchClause"
780
+ ].includes(scope.type))) scope = getParent(scope);
781
+ return scope;
782
+ };
783
+ const lookup = (node, name) => {
784
+ for (let scope = node; scope != null; scope = getParent(scope)) {
785
+ const binding = scopeBindings(scope).get(name);
786
+ if (binding != null) return binding;
755
787
  }
788
+ return null;
789
+ };
790
+ return {
791
+ lookup,
792
+ declare(node, pattern, value, hoisted = false) {
793
+ const bindings = scopeBindings(enclosingScope(node, hoisted));
794
+ if (pattern.type === "Identifier") {
795
+ if (hoisted && value == null && bindings.has(pattern.name)) return;
796
+ if (node.type === "FunctionDeclaration" && bindings.get(pattern.name)?.initializedVariable) return;
797
+ bindings.set(pattern.name, {
798
+ value,
799
+ initializedVariable: node.type === "VariableDeclarator" && value != null
800
+ });
801
+ } else bindPattern(pattern, bindings);
802
+ }
803
+ };
804
+ }
805
+ const position = (node) => node.range?.[0] ?? node.start ?? -1;
806
+ const endPosition = (node) => node.range?.[1] ?? node.end ?? -1;
807
+ const enclosingFunction = (node) => {
808
+ for (let parent = getParent(node); parent != null; parent = getParent(parent)) if (isFunctionLike(parent)) return parent;
809
+ return null;
810
+ };
811
+ const isInside = (node, ancestor) => {
812
+ for (let current = node; current != null; current = getParent(current)) if (current === ancestor) return true;
813
+ return false;
814
+ };
815
+ const isDirectStatement = (node, fn) => {
816
+ let current = node;
817
+ while (getParent(current) !== fn.body) {
818
+ const parent = getParent(current);
819
+ if (parent == null || ![
820
+ "AwaitExpression",
821
+ "ExpressionStatement",
822
+ "ReturnStatement",
823
+ "VariableDeclarator",
824
+ "VariableDeclaration",
825
+ "BlockStatement"
826
+ ].includes(parent.type)) return false;
827
+ current = parent;
828
+ }
829
+ return [
830
+ "ExpressionStatement",
831
+ "ReturnStatement",
832
+ "VariableDeclaration",
833
+ "BlockStatement"
834
+ ].includes(current.type);
835
+ };
836
+ const resolveBindingValue = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
837
+ if (expr.type !== "Identifier") return expr;
838
+ const binding = bindings.lookup(expr, expr.name);
839
+ if (binding == null || binding.value == null || seen.has(binding)) return null;
840
+ seen.add(binding);
841
+ return resolveBindingValue(binding.value, bindings, seen);
842
+ };
843
+ const resolveListenerReference = (expr, bindings) => {
844
+ const target = resolveBindingValue(expr, bindings);
845
+ if (target == null) return null;
846
+ if (isFunctionLike(target)) return target;
847
+ if (target.type !== "MemberExpression") return null;
848
+ const object = resolveBindingValue(target.object, bindings);
849
+ if (object?.type !== "ObjectExpression") return null;
850
+ const propertyName = getMemberPropertyName(target);
851
+ if (propertyName == null) return null;
852
+ for (const prop of object.properties) {
853
+ if (prop.type !== "Property") continue;
854
+ if ((prop.key.type === "Identifier" && !prop.computed ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null) !== propertyName) continue;
855
+ const value = resolveBindingValue(prop.value, bindings);
856
+ if (value != null && isFunctionLike(value)) return value;
756
857
  }
757
858
  return null;
758
859
  };
759
- const listenerCallsDeliveryMethod = (sourceCode, listener) => {
760
- const code = stripCommentsAndStrings(sourceCode.getText(listener));
860
+ function unwrapArgument(node) {
861
+ while (node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") node = node.expression;
862
+ return node;
863
+ }
864
+ function ignoredLocalSetupFunctions(listener, bindings, calls, assignments) {
865
+ const ignored = [];
866
+ const directCalls = calls.filter((call) => enclosingFunction(call) === listener && isDirectStatement(call, listener));
867
+ for (const assignment of assignments) {
868
+ if (assignment.type !== "AssignmentExpression" || assignment.operator !== "=" || assignment.left.type !== "MemberExpression" || !isFunction(assignment.right)) continue;
869
+ const setup = enclosingFunction(assignment);
870
+ if (setup == null || setup === listener || !isInside(setup, listener)) continue;
871
+ const setupParent = getParent(setup);
872
+ const namedLocalSetup = enclosingFunction(setup) === listener && (setup.type === "FunctionDeclaration" || setupParent?.type === "VariableDeclarator" && setupParent.init === setup && setupParent.id.type === "Identifier");
873
+ const passedToCall = calls.some((call) => call.arguments.some((argument) => argument.type !== "SpreadElement" && resolveListenerReference(argument, bindings) === setup));
874
+ if (!namedLocalSetup || passedToCall) continue;
875
+ const installed = assignment.right;
876
+ if (!isDirectStatement(assignment, setup) || assignment.left.object.type !== "Identifier") {
877
+ ignored.push(installed);
878
+ continue;
879
+ }
880
+ const property = getMemberPropertyName(assignment.left);
881
+ if (property == null) continue;
882
+ const setupCalls = directCalls.filter((call) => resolveListenerReference(call.callee, bindings) === setup);
883
+ if (setupCalls.length === 0) {
884
+ const called = calls.some((call) => resolveListenerReference(call.callee, bindings) === setup);
885
+ ignored.push(called ? assignment.right : setup);
886
+ continue;
887
+ }
888
+ const objectBinding = bindings.lookup(assignment.left.object, assignment.left.object.name);
889
+ const deliveryCalls = directCalls.filter((call) => {
890
+ const callee = call.callee;
891
+ return callee.type === "MemberExpression" && callee.object.type === "Identifier" && getMemberPropertyName(callee) === property && bindings.lookup(callee.object, callee.object.name) === objectBinding;
892
+ });
893
+ if (!(objectBinding != null && setupCalls.some((setupCall) => deliveryCalls.some((deliveryCall) => position(setupCall) >= 0 && position(setupCall) < position(deliveryCall))))) ignored.push(installed);
894
+ }
895
+ return ignored;
896
+ }
897
+ function functionCallsDelivery(sourceCode, listener, bindings, calls, assignments, contextIndex = 0, visited = /* @__PURE__ */ new Map()) {
898
+ let indices = visited.get(listener);
899
+ if (indices?.has(contextIndex)) return false;
900
+ if (indices == null) visited.set(listener, indices = /* @__PURE__ */ new Set());
901
+ indices.add(contextIndex);
902
+ if (listenerCallsDeliveryMethod(sourceCode, listener, contextIndex, ignoredLocalSetupFunctions(listener, bindings, calls, assignments))) return true;
903
+ const param = unwrapContextParam(listener.params[contextIndex]);
904
+ if (param?.type !== "Identifier") return false;
905
+ const contextBinding = bindings.lookup(listener, param.name);
906
+ for (const call of calls) {
907
+ let child = call;
908
+ let owner = getParent(child);
909
+ while (owner != null && !isFunctionLike(owner)) {
910
+ const field = owner;
911
+ if ((field.type === "PropertyDefinition" || field.type === "AccessorProperty") && !field.static && field.value === child) break;
912
+ child = owner;
913
+ owner = getParent(owner);
914
+ }
915
+ if (owner !== listener) continue;
916
+ const helper = resolveListenerReference(call.callee, bindings);
917
+ if (helper == null || "generator" in helper && helper.generator) continue;
918
+ for (const [index, argument] of call.arguments.entries()) {
919
+ if (argument.type === "SpreadElement") break;
920
+ const arg = unwrapArgument(argument);
921
+ if (arg.type !== "Identifier" || arg.name !== param.name || bindings.lookup(arg, arg.name) !== contextBinding || helper.params[index]?.type === "RestElement") continue;
922
+ if (functionCallsDelivery(sourceCode, helper, bindings, calls, assignments, index, visited)) return true;
923
+ }
924
+ }
925
+ return false;
926
+ }
927
+ const listenerCallsDeliveryMethod = (sourceCode, listener, contextIndex = 0, ignored = []) => {
928
+ let text = sourceCode.getText(listener);
929
+ const start = position(listener);
930
+ for (const fn of ignored) {
931
+ const from = position(fn) - start;
932
+ const to = endPosition(fn) - start;
933
+ if (from < 0 || to > text.length || to < from) continue;
934
+ text = text.slice(0, from) + " ".repeat(to - from) + text.slice(to);
935
+ }
936
+ const code = stripCommentsAndStrings(text);
761
937
  const aliases = /* @__PURE__ */ new Set();
762
- const contextParam = unwrapContextParam(listener.params[0]);
938
+ const contextParam = unwrapContextParam(listener.params[contextIndex]);
763
939
  const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
764
940
  if (contextParam?.type === "ObjectPattern") for (const prop of contextParam.properties) {
765
941
  if (!isNode(prop) || prop.type !== "Property") continue;
@@ -788,8 +964,9 @@ const listenerCallsDeliveryMethod = (sourceCode, listener) => {
788
964
  function createRule(buildReport) {
789
965
  return (context) => {
790
966
  const federationTracker = trackFederationVariables();
791
- const bindings = /* @__PURE__ */ new Map();
967
+ const bindings = createBindingIndex();
792
968
  const pendingCalls = [];
969
+ const pendingAssignments = [];
793
970
  const sourceCode = context.sourceCode;
794
971
  const inspectCall = (node) => {
795
972
  if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
@@ -797,7 +974,7 @@ function createRule(buildReport) {
797
974
  const listener = node.arguments[1];
798
975
  const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveListenerReference(listener, bindings) : null;
799
976
  if (resolvedListener == null) return;
800
- if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
977
+ if (functionCallsDelivery(sourceCode, resolvedListener, bindings, pendingCalls, pendingAssignments)) return;
801
978
  context.report({
802
979
  node: resolvedListener,
803
980
  ...buildReport
@@ -806,14 +983,28 @@ function createRule(buildReport) {
806
983
  return {
807
984
  VariableDeclarator(node) {
808
985
  federationTracker.VariableDeclarator(node);
809
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
986
+ const declaration = getParent(node);
987
+ bindings.declare(node, node.id, node.init, declaration?.kind === "var");
810
988
  },
811
989
  FunctionDeclaration(node) {
812
- if (node.id != null) bindings.set(node.id.name, node);
990
+ if (node.id == null) return;
991
+ const parent = getParent(node);
992
+ const owner = parent == null ? null : getParent(parent);
993
+ const inVarBody = parent?.type === "BlockStatement" && owner != null && (owner.type === "StaticBlock" || isFunctionLike(owner) && owner.body === parent);
994
+ bindings.declare(node, node.id, node, inVarBody);
995
+ },
996
+ ImportDeclaration(node) {
997
+ for (const specifier of node.specifiers) bindings.declare(node, specifier.local, null);
998
+ },
999
+ ClassDeclaration(node) {
1000
+ if (node.id != null) bindings.declare(node, node.id, null);
813
1001
  },
814
1002
  CallExpression(node) {
815
1003
  pendingCalls.push(node);
816
1004
  },
1005
+ AssignmentExpression(node) {
1006
+ pendingAssignments.push(node);
1007
+ },
817
1008
  "Program:exit"() {
818
1009
  for (const node of pendingCalls) inspectCall(node);
819
1010
  }
@@ -2,7 +2,7 @@ require("./index.cjs");
2
2
  let _fxts_core = require("@fxts/core");
3
3
  //#region deno.json
4
4
  var name = "@fedify/lint";
5
- var version = "2.3.9-dev.2048+c88fcc8c";
5
+ var version = "2.3.10";
6
6
  //#endregion
7
7
  //#region src/lib/const.ts
8
8
  /**
@@ -596,7 +596,7 @@ const DELIVERY_METHOD_NAMES = new Set(["sendActivity", "forwardActivity"]);
596
596
  const getMemberPropertyName = (expr) => {
597
597
  if (expr.type !== "MemberExpression") return null;
598
598
  const property = expr.property;
599
- if (property.type === "Identifier") return property.name;
599
+ if (property.type === "Identifier" && !expr.computed) return property.name;
600
600
  if (property.type === "Literal" && typeof property.value === "string") return property.value;
601
601
  return null;
602
602
  };
@@ -732,35 +732,211 @@ function buildContextExpressionPattern(contextName) {
732
732
  const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
733
733
  return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
734
734
  }
735
- const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
736
- if (isFunction(expr)) return expr;
737
- if (expr.type === "Identifier") {
738
- if (seen.has(expr.name)) return null;
739
- seen.add(expr.name);
740
- const binding = bindings.get(expr.name);
741
- if (binding == null || !isNode(binding)) return null;
742
- if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
743
- if (binding.type === "Identifier") return resolveListenerReference(binding, bindings, seen);
744
- return null;
735
+ const getParent = (node) => node.parent ?? null;
736
+ const isFunctionLike = (node) => node.type === "FunctionDeclaration" || isFunction(node);
737
+ function bindPattern(node, bindings) {
738
+ switch (node.type) {
739
+ case "Identifier":
740
+ bindings.set(node.name, { value: null });
741
+ break;
742
+ case "AssignmentPattern":
743
+ bindPattern(node.left, bindings);
744
+ break;
745
+ case "RestElement":
746
+ bindPattern(node.argument, bindings);
747
+ break;
748
+ case "ObjectPattern":
749
+ for (const property of node.properties) bindPattern(property.type === "Property" ? property.value : property, bindings);
750
+ break;
751
+ case "ArrayPattern":
752
+ for (const element of node.elements) if (element != null) bindPattern(element, bindings);
753
+ break;
745
754
  }
746
- if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
747
- const binding = bindings.get(expr.object.name);
748
- if (binding == null || !isNode(binding) || binding.type !== "ObjectExpression") return null;
749
- const propertyName = getMemberPropertyName(expr);
750
- if (propertyName == null) return null;
751
- for (const prop of binding.properties) {
752
- if (!isNode(prop) || prop.type !== "Property") continue;
753
- if ((prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null) !== propertyName || !isNode(prop.value)) continue;
754
- const value = prop.value;
755
- if (isFunction(value) || value.type === "FunctionDeclaration") return value;
755
+ }
756
+ function createBindingIndex() {
757
+ const scopes = /* @__PURE__ */ new Map();
758
+ const scopeBindings = (scope) => {
759
+ let bindings = scopes.get(scope);
760
+ if (bindings == null) {
761
+ bindings = /* @__PURE__ */ new Map();
762
+ scopes.set(scope, bindings);
763
+ if (isFunctionLike(scope)) {
764
+ if (scope.type === "FunctionExpression" && scope.id != null) bindings.set(scope.id.name, { value: scope });
765
+ for (const param of scope.params) bindPattern(param, bindings);
766
+ }
767
+ if (scope.type === "ClassExpression" && scope.id != null) bindings.set(scope.id.name, { value: null });
768
+ if (scope.type === "CatchClause" && scope.param != null) bindPattern(scope.param, bindings);
769
+ }
770
+ return bindings;
771
+ };
772
+ const enclosingScope = (node, functionScope = false) => {
773
+ let scope = getParent(node);
774
+ while (scope.type !== "Program" && scope.type !== "StaticBlock" && scope.type !== "TSModuleBlock" && !isFunctionLike(scope) && (functionScope || ![
775
+ "BlockStatement",
776
+ "ForStatement",
777
+ "ForInStatement",
778
+ "ForOfStatement",
779
+ "SwitchStatement",
780
+ "CatchClause"
781
+ ].includes(scope.type))) scope = getParent(scope);
782
+ return scope;
783
+ };
784
+ const lookup = (node, name) => {
785
+ for (let scope = node; scope != null; scope = getParent(scope)) {
786
+ const binding = scopeBindings(scope).get(name);
787
+ if (binding != null) return binding;
756
788
  }
789
+ return null;
790
+ };
791
+ return {
792
+ lookup,
793
+ declare(node, pattern, value, hoisted = false) {
794
+ const bindings = scopeBindings(enclosingScope(node, hoisted));
795
+ if (pattern.type === "Identifier") {
796
+ if (hoisted && value == null && bindings.has(pattern.name)) return;
797
+ if (node.type === "FunctionDeclaration" && bindings.get(pattern.name)?.initializedVariable) return;
798
+ bindings.set(pattern.name, {
799
+ value,
800
+ initializedVariable: node.type === "VariableDeclarator" && value != null
801
+ });
802
+ } else bindPattern(pattern, bindings);
803
+ }
804
+ };
805
+ }
806
+ const position = (node) => node.range?.[0] ?? node.start ?? -1;
807
+ const endPosition = (node) => node.range?.[1] ?? node.end ?? -1;
808
+ const enclosingFunction = (node) => {
809
+ for (let parent = getParent(node); parent != null; parent = getParent(parent)) if (isFunctionLike(parent)) return parent;
810
+ return null;
811
+ };
812
+ const isInside = (node, ancestor) => {
813
+ for (let current = node; current != null; current = getParent(current)) if (current === ancestor) return true;
814
+ return false;
815
+ };
816
+ const isDirectStatement = (node, fn) => {
817
+ let current = node;
818
+ while (getParent(current) !== fn.body) {
819
+ const parent = getParent(current);
820
+ if (parent == null || ![
821
+ "AwaitExpression",
822
+ "ExpressionStatement",
823
+ "ReturnStatement",
824
+ "VariableDeclarator",
825
+ "VariableDeclaration",
826
+ "BlockStatement"
827
+ ].includes(parent.type)) return false;
828
+ current = parent;
829
+ }
830
+ return [
831
+ "ExpressionStatement",
832
+ "ReturnStatement",
833
+ "VariableDeclaration",
834
+ "BlockStatement"
835
+ ].includes(current.type);
836
+ };
837
+ const resolveBindingValue = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
838
+ if (expr.type !== "Identifier") return expr;
839
+ const binding = bindings.lookup(expr, expr.name);
840
+ if (binding == null || binding.value == null || seen.has(binding)) return null;
841
+ seen.add(binding);
842
+ return resolveBindingValue(binding.value, bindings, seen);
843
+ };
844
+ const resolveListenerReference = (expr, bindings) => {
845
+ const target = resolveBindingValue(expr, bindings);
846
+ if (target == null) return null;
847
+ if (isFunctionLike(target)) return target;
848
+ if (target.type !== "MemberExpression") return null;
849
+ const object = resolveBindingValue(target.object, bindings);
850
+ if (object?.type !== "ObjectExpression") return null;
851
+ const propertyName = getMemberPropertyName(target);
852
+ if (propertyName == null) return null;
853
+ for (const prop of object.properties) {
854
+ if (prop.type !== "Property") continue;
855
+ if ((prop.key.type === "Identifier" && !prop.computed ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null) !== propertyName) continue;
856
+ const value = resolveBindingValue(prop.value, bindings);
857
+ if (value != null && isFunctionLike(value)) return value;
757
858
  }
758
859
  return null;
759
860
  };
760
- const listenerCallsDeliveryMethod = (sourceCode, listener) => {
761
- const code = stripCommentsAndStrings(sourceCode.getText(listener));
861
+ function unwrapArgument(node) {
862
+ while (node.type === "TSAsExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") node = node.expression;
863
+ return node;
864
+ }
865
+ function ignoredLocalSetupFunctions(listener, bindings, calls, assignments) {
866
+ const ignored = [];
867
+ const directCalls = calls.filter((call) => enclosingFunction(call) === listener && isDirectStatement(call, listener));
868
+ for (const assignment of assignments) {
869
+ if (assignment.type !== "AssignmentExpression" || assignment.operator !== "=" || assignment.left.type !== "MemberExpression" || !isFunction(assignment.right)) continue;
870
+ const setup = enclosingFunction(assignment);
871
+ if (setup == null || setup === listener || !isInside(setup, listener)) continue;
872
+ const setupParent = getParent(setup);
873
+ const namedLocalSetup = enclosingFunction(setup) === listener && (setup.type === "FunctionDeclaration" || setupParent?.type === "VariableDeclarator" && setupParent.init === setup && setupParent.id.type === "Identifier");
874
+ const passedToCall = calls.some((call) => call.arguments.some((argument) => argument.type !== "SpreadElement" && resolveListenerReference(argument, bindings) === setup));
875
+ if (!namedLocalSetup || passedToCall) continue;
876
+ const installed = assignment.right;
877
+ if (!isDirectStatement(assignment, setup) || assignment.left.object.type !== "Identifier") {
878
+ ignored.push(installed);
879
+ continue;
880
+ }
881
+ const property = getMemberPropertyName(assignment.left);
882
+ if (property == null) continue;
883
+ const setupCalls = directCalls.filter((call) => resolveListenerReference(call.callee, bindings) === setup);
884
+ if (setupCalls.length === 0) {
885
+ const called = calls.some((call) => resolveListenerReference(call.callee, bindings) === setup);
886
+ ignored.push(called ? assignment.right : setup);
887
+ continue;
888
+ }
889
+ const objectBinding = bindings.lookup(assignment.left.object, assignment.left.object.name);
890
+ const deliveryCalls = directCalls.filter((call) => {
891
+ const callee = call.callee;
892
+ return callee.type === "MemberExpression" && callee.object.type === "Identifier" && getMemberPropertyName(callee) === property && bindings.lookup(callee.object, callee.object.name) === objectBinding;
893
+ });
894
+ if (!(objectBinding != null && setupCalls.some((setupCall) => deliveryCalls.some((deliveryCall) => position(setupCall) >= 0 && position(setupCall) < position(deliveryCall))))) ignored.push(installed);
895
+ }
896
+ return ignored;
897
+ }
898
+ function functionCallsDelivery(sourceCode, listener, bindings, calls, assignments, contextIndex = 0, visited = /* @__PURE__ */ new Map()) {
899
+ let indices = visited.get(listener);
900
+ if (indices?.has(contextIndex)) return false;
901
+ if (indices == null) visited.set(listener, indices = /* @__PURE__ */ new Set());
902
+ indices.add(contextIndex);
903
+ if (listenerCallsDeliveryMethod(sourceCode, listener, contextIndex, ignoredLocalSetupFunctions(listener, bindings, calls, assignments))) return true;
904
+ const param = unwrapContextParam(listener.params[contextIndex]);
905
+ if (param?.type !== "Identifier") return false;
906
+ const contextBinding = bindings.lookup(listener, param.name);
907
+ for (const call of calls) {
908
+ let child = call;
909
+ let owner = getParent(child);
910
+ while (owner != null && !isFunctionLike(owner)) {
911
+ const field = owner;
912
+ if ((field.type === "PropertyDefinition" || field.type === "AccessorProperty") && !field.static && field.value === child) break;
913
+ child = owner;
914
+ owner = getParent(owner);
915
+ }
916
+ if (owner !== listener) continue;
917
+ const helper = resolveListenerReference(call.callee, bindings);
918
+ if (helper == null || "generator" in helper && helper.generator) continue;
919
+ for (const [index, argument] of call.arguments.entries()) {
920
+ if (argument.type === "SpreadElement") break;
921
+ const arg = unwrapArgument(argument);
922
+ if (arg.type !== "Identifier" || arg.name !== param.name || bindings.lookup(arg, arg.name) !== contextBinding || helper.params[index]?.type === "RestElement") continue;
923
+ if (functionCallsDelivery(sourceCode, helper, bindings, calls, assignments, index, visited)) return true;
924
+ }
925
+ }
926
+ return false;
927
+ }
928
+ const listenerCallsDeliveryMethod = (sourceCode, listener, contextIndex = 0, ignored = []) => {
929
+ let text = sourceCode.getText(listener);
930
+ const start = position(listener);
931
+ for (const fn of ignored) {
932
+ const from = position(fn) - start;
933
+ const to = endPosition(fn) - start;
934
+ if (from < 0 || to > text.length || to < from) continue;
935
+ text = text.slice(0, from) + " ".repeat(to - from) + text.slice(to);
936
+ }
937
+ const code = stripCommentsAndStrings(text);
762
938
  const aliases = /* @__PURE__ */ new Set();
763
- const contextParam = unwrapContextParam(listener.params[0]);
939
+ const contextParam = unwrapContextParam(listener.params[contextIndex]);
764
940
  const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
765
941
  if (contextParam?.type === "ObjectPattern") for (const prop of contextParam.properties) {
766
942
  if (!isNode(prop) || prop.type !== "Property") continue;
@@ -789,8 +965,9 @@ const listenerCallsDeliveryMethod = (sourceCode, listener) => {
789
965
  function createRule(buildReport) {
790
966
  return (context) => {
791
967
  const federationTracker = trackFederationVariables();
792
- const bindings = /* @__PURE__ */ new Map();
968
+ const bindings = createBindingIndex();
793
969
  const pendingCalls = [];
970
+ const pendingAssignments = [];
794
971
  const sourceCode = context.sourceCode;
795
972
  const inspectCall = (node) => {
796
973
  if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
@@ -798,7 +975,7 @@ function createRule(buildReport) {
798
975
  const listener = node.arguments[1];
799
976
  const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveListenerReference(listener, bindings) : null;
800
977
  if (resolvedListener == null) return;
801
- if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
978
+ if (functionCallsDelivery(sourceCode, resolvedListener, bindings, pendingCalls, pendingAssignments)) return;
802
979
  context.report({
803
980
  node: resolvedListener,
804
981
  ...buildReport
@@ -807,14 +984,28 @@ function createRule(buildReport) {
807
984
  return {
808
985
  VariableDeclarator(node) {
809
986
  federationTracker.VariableDeclarator(node);
810
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
987
+ const declaration = getParent(node);
988
+ bindings.declare(node, node.id, node.init, declaration?.kind === "var");
811
989
  },
812
990
  FunctionDeclaration(node) {
813
- if (node.id != null) bindings.set(node.id.name, node);
991
+ if (node.id == null) return;
992
+ const parent = getParent(node);
993
+ const owner = parent == null ? null : getParent(parent);
994
+ const inVarBody = parent?.type === "BlockStatement" && owner != null && (owner.type === "StaticBlock" || isFunctionLike(owner) && owner.body === parent);
995
+ bindings.declare(node, node.id, node, inVarBody);
996
+ },
997
+ ImportDeclaration(node) {
998
+ for (const specifier of node.specifiers) bindings.declare(node, specifier.local, null);
999
+ },
1000
+ ClassDeclaration(node) {
1001
+ if (node.id != null) bindings.declare(node, node.id, null);
814
1002
  },
815
1003
  CallExpression(node) {
816
1004
  pendingCalls.push(node);
817
1005
  },
1006
+ AssignmentExpression(node) {
1007
+ pendingAssignments.push(node);
1008
+ },
818
1009
  "Program:exit"() {
819
1010
  for (const node of pendingCalls) inspectCall(node);
820
1011
  }
package/dist/oxlint.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-C1Ho3dCQ.cjs");
1
+ const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-CwToC306.cjs");
2
2
  //#region src/oxlint.ts
3
3
  const rules = {
4
4
  [require_outbox_listener_delivery_required.RULE_IDS.actorIdMismatch]: require_outbox_listener_delivery_required.eslint$12,
package/dist/oxlint.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as RULE_IDS, S as eslint$19, T as version, _ as eslint$5, a as eslint$18, b as eslint$10, c as eslint$8, d as eslint$15, f as eslint$1, g as eslint$4, h as eslint$3, i as eslint$17, l as eslint$9, m as eslint$2, n as eslint$20, o as eslint$6, p as eslint, r as eslint$16, s as eslint$7, t as eslint$21, u as eslint$14, v as eslint$12, w as name, x as eslint$11, y as eslint$13 } from "./outbox-listener-delivery-required-CxugrN84.js";
1
+ import { C as RULE_IDS, S as eslint$19, T as version, _ as eslint$5, a as eslint$18, b as eslint$10, c as eslint$8, d as eslint$15, f as eslint$1, g as eslint$4, h as eslint$3, i as eslint$17, l as eslint$9, m as eslint$2, n as eslint$20, o as eslint$6, p as eslint, r as eslint$16, s as eslint$7, t as eslint$21, u as eslint$14, v as eslint$12, w as name, x as eslint$11, y as eslint$13 } from "./outbox-listener-delivery-required-BNgI_uik.js";
2
2
  //#region src/oxlint.ts
3
3
  const rules = {
4
4
  [RULE_IDS.actorIdMismatch]: eslint,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/lint",
3
- "version": "2.3.9-dev.2048+c88fcc8c",
3
+ "version": "2.3.10",
4
4
  "description": "Fedify linting rules and plugins",
5
5
  "keywords": [
6
6
  "Fedify",
@@ -54,7 +54,7 @@
54
54
  ],
55
55
  "peerDependencies": {
56
56
  "eslint": ">=9.0.0",
57
- "@fedify/fedify": "^2.3.9-dev.2048+c88fcc8c"
57
+ "@fedify/fedify": "^2.3.10"
58
58
  },
59
59
  "peerDependenciesMeta": {
60
60
  "eslint": {