@fedify/lint 2.2.0-dev.844 → 2.2.0-dev.851

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/lint",
3
- "version": "2.2.0-dev.844+6cc39f0b",
3
+ "version": "2.2.0-dev.851+223698da",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts"
package/dist/index.cjs CHANGED
@@ -29,7 +29,7 @@ let _typescript_eslint_parser = require("@typescript-eslint/parser");
29
29
  _typescript_eslint_parser = __toESM(_typescript_eslint_parser);
30
30
  //#region deno.json
31
31
  var name = "@fedify/lint";
32
- var version = "2.2.0-dev.844+6cc39f0b";
32
+ var version = "2.2.0-dev.851+223698da";
33
33
  //#endregion
34
34
  //#region src/lib/const.ts
35
35
  /**
@@ -146,8 +146,7 @@ const RULE_IDS = {
146
146
  actorFeaturedTagsPropertyMismatch: "actor-featured-tags-property-mismatch",
147
147
  actorInboxPropertyMismatch: "actor-inbox-property-mismatch",
148
148
  actorSharedInboxPropertyMismatch: "actor-shared-inbox-property-mismatch",
149
- collectionFilteringNotImplemented: "collection-filtering-not-implemented",
150
- outboxListenerDeliveryRequired: "outbox-listener-delivery-required"
149
+ collectionFilteringNotImplemented: "collection-filtering-not-implemented"
151
150
  };
152
151
  //#endregion
153
152
  //#region src/lib/messages.ts
@@ -192,7 +191,6 @@ function allOf(...predicates) {
192
191
  return (value) => predicates.every((predicate) => predicate(value));
193
192
  }
194
193
  const anyOf = (...predicates) => (value) => predicates.some((predicate) => predicate(value));
195
- const isNode = (obj) => (0, _fxts_core.isObject)(obj) && "type" in obj;
196
194
  /**
197
195
  * Checks if a node is of a specific type.
198
196
  */
@@ -445,7 +443,7 @@ function createRequiredRuleEslint(config) {
445
443
  };
446
444
  }
447
445
  createRequiredRuleDeno(properties.assertionMethod);
448
- const eslint$21 = createRequiredRuleEslint(properties.assertionMethod);
446
+ const eslint$20 = createRequiredRuleEslint(properties.assertionMethod);
449
447
  //#endregion
450
448
  //#region src/lib/mismatch.ts
451
449
  const isIdentifierWithName = (name) => (node) => allOf(isNodeType("Identifier"), isNodeName(name))(node);
@@ -515,43 +513,43 @@ const createMismatchRuleEslint = (config) => ({
515
513
  }))
516
514
  });
517
515
  createMismatchRuleDeno(properties.featured);
518
- const eslint$20 = createMismatchRuleEslint(properties.featured);
516
+ const eslint$19 = createMismatchRuleEslint(properties.featured);
519
517
  createRequiredRuleDeno(properties.featured);
520
- const eslint$19 = createRequiredRuleEslint(properties.featured);
518
+ const eslint$18 = createRequiredRuleEslint(properties.featured);
521
519
  createMismatchRuleDeno(properties.featuredTags);
522
- const eslint$18 = createMismatchRuleEslint(properties.featuredTags);
520
+ const eslint$17 = createMismatchRuleEslint(properties.featuredTags);
523
521
  createRequiredRuleDeno(properties.featuredTags);
524
- const eslint$17 = createRequiredRuleEslint(properties.featuredTags);
522
+ const eslint$16 = createRequiredRuleEslint(properties.featuredTags);
525
523
  createMismatchRuleDeno(properties.followers);
526
- const eslint$16 = createMismatchRuleEslint(properties.followers);
524
+ const eslint$15 = createMismatchRuleEslint(properties.followers);
527
525
  createRequiredRuleDeno(properties.followers);
528
- const eslint$15 = createRequiredRuleEslint(properties.followers);
526
+ const eslint$14 = createRequiredRuleEslint(properties.followers);
529
527
  createMismatchRuleDeno(properties.following);
530
- const eslint$14 = createMismatchRuleEslint(properties.following);
528
+ const eslint$13 = createMismatchRuleEslint(properties.following);
531
529
  createRequiredRuleDeno(properties.following);
532
- const eslint$13 = createRequiredRuleEslint(properties.following);
530
+ const eslint$12 = createRequiredRuleEslint(properties.following);
533
531
  createMismatchRuleDeno(properties.id);
534
- const eslint$12 = createMismatchRuleEslint(properties.id);
532
+ const eslint$11 = createMismatchRuleEslint(properties.id);
535
533
  createRequiredRuleDeno(properties.id);
536
- const eslint$11 = createRequiredRuleEslint(properties.id);
534
+ const eslint$10 = createRequiredRuleEslint(properties.id);
537
535
  createMismatchRuleDeno(properties.inbox);
538
- const eslint$10 = createMismatchRuleEslint(properties.inbox);
536
+ const eslint$9 = createMismatchRuleEslint(properties.inbox);
539
537
  createRequiredRuleDeno(properties.inbox);
540
- const eslint$9 = createRequiredRuleEslint(properties.inbox);
538
+ const eslint$8 = createRequiredRuleEslint(properties.inbox);
541
539
  createMismatchRuleDeno(properties.liked);
542
- const eslint$8 = createMismatchRuleEslint(properties.liked);
540
+ const eslint$7 = createMismatchRuleEslint(properties.liked);
543
541
  createRequiredRuleDeno(properties.liked);
544
- const eslint$7 = createRequiredRuleEslint(properties.liked);
542
+ const eslint$6 = createRequiredRuleEslint(properties.liked);
545
543
  createMismatchRuleDeno(properties.outbox);
546
- const eslint$6 = createMismatchRuleEslint(properties.outbox);
544
+ const eslint$5 = createMismatchRuleEslint(properties.outbox);
547
545
  createRequiredRuleDeno(properties.outbox);
548
- const eslint$5 = createRequiredRuleEslint(properties.outbox);
546
+ const eslint$4 = createRequiredRuleEslint(properties.outbox);
549
547
  createRequiredRuleDeno(properties.publicKey);
550
- const eslint$4 = createRequiredRuleEslint(properties.publicKey);
548
+ const eslint$3 = createRequiredRuleEslint(properties.publicKey);
551
549
  createMismatchRuleDeno(properties.sharedInbox);
552
- const eslint$3 = createMismatchRuleEslint(properties.sharedInbox);
550
+ const eslint$2 = createMismatchRuleEslint(properties.sharedInbox);
553
551
  createRequiredRuleDeno(properties.sharedInbox);
554
- const eslint$2 = createRequiredRuleEslint(properties.sharedInbox);
552
+ const eslint$1 = createRequiredRuleEslint(properties.sharedInbox);
555
553
  //#endregion
556
554
  //#region src/rules/collection-filtering-not-implemented.ts
557
555
  /**
@@ -570,7 +568,7 @@ const isFollowersDispatcherCall = (node) => "callee" in node && node.callee && n
570
568
  * CollectionDispatcher signature: (context, identifier, cursor, filter?) => ...
571
569
  */
572
570
  const hasFilterParameter = hasMinParams(4);
573
- const eslint$1 = {
571
+ const eslint = {
574
572
  meta: {
575
573
  type: "suggestion",
576
574
  docs: { description: "Ensure followers dispatcher implements filtering" },
@@ -595,287 +593,33 @@ const eslint$1 = {
595
593
  }
596
594
  };
597
595
  //#endregion
598
- //#region src/rules/outbox-listener-delivery-required.ts
599
- const MESSAGE = "Outbox listeners should deliver posted activities explicitly with ctx.sendActivity() or ctx.forwardActivity().";
600
- const isChainedFromOutboxListeners = (expr, federationTracker) => {
601
- if (expr.type !== "CallExpression") return false;
602
- if (!hasMemberExpressionCallee(expr) || !hasIdentifierProperty(expr)) return false;
603
- const methodName = expr.callee.property.name;
604
- if (methodName === "setOutboxListeners") return federationTracker.isFederationObject(expr.callee.object);
605
- if (methodName === "authorize" || methodName === "onError" || methodName === "on") return isChainedFromOutboxListeners(expr.callee.object, federationTracker);
606
- return false;
607
- };
608
- const DELIVERY_METHOD_NAMES = new Set(["sendActivity", "forwardActivity"]);
609
- const getMemberPropertyName = (expr) => {
610
- if (expr.type !== "MemberExpression") return null;
611
- const property = expr.property;
612
- if (property.type === "Identifier") return property.name;
613
- if (property.type === "Literal" && typeof property.value === "string") return property.value;
614
- return null;
615
- };
616
- function unwrapContextParam(node) {
617
- let current = node ?? null;
618
- while (current?.type === "AssignmentPattern") current = current.left;
619
- return current;
620
- }
621
- function escapeRegExp(value) {
622
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
623
- }
624
- function stripCommentsAndStrings(code) {
625
- let result = "";
626
- let index = 0;
627
- const skipQuotedString = (quote) => {
628
- const start = index;
629
- index += 1;
630
- while (index < code.length) {
631
- const char = code[index];
632
- if (char === "\\") {
633
- index += 2;
634
- continue;
635
- }
636
- index += 1;
637
- if (char === quote) break;
638
- }
639
- const literal = code.slice(start, index);
640
- const value = literal.slice(1, -1);
641
- result += DELIVERY_METHOD_NAMES.has(value) ? literal : `${quote}${quote}`;
642
- };
643
- const stripTemplateLiteral = () => {
644
- const start = index;
645
- index += 1;
646
- let raw = "";
647
- let hasExpression = false;
648
- while (index < code.length) {
649
- const char = code[index];
650
- if (char === "\\") {
651
- raw += char;
652
- raw += code[index + 1] ?? "";
653
- index += 2;
654
- continue;
655
- }
656
- if (char === "`") {
657
- index += 1;
658
- if (!hasExpression && DELIVERY_METHOD_NAMES.has(raw)) result += code.slice(start, index);
659
- else result += "``";
660
- return;
661
- }
662
- if (char === "$" && code[index + 1] === "{") {
663
- hasExpression = true;
664
- result += "`${";
665
- index += 2;
666
- let depth = 1;
667
- while (index < code.length && depth > 0) {
668
- const exprChar = code[index];
669
- const next = code[index + 1];
670
- if (exprChar === "'" || exprChar === "\"") {
671
- skipQuotedString(exprChar);
672
- continue;
673
- }
674
- if (exprChar === "`") {
675
- stripTemplateLiteral();
676
- continue;
677
- }
678
- if (exprChar === "/" && next === "*") {
679
- index += 2;
680
- while (index < code.length) {
681
- if (code[index] === "*" && code[index + 1] === "/") {
682
- index += 2;
683
- break;
684
- }
685
- index += 1;
686
- }
687
- continue;
688
- }
689
- if (exprChar === "/" && next === "/") {
690
- index += 2;
691
- while (index < code.length && code[index] !== "\n") index += 1;
692
- continue;
693
- }
694
- result += exprChar;
695
- index += 1;
696
- if (exprChar === "{") depth += 1;
697
- else if (exprChar === "}") depth -= 1;
698
- }
699
- continue;
700
- }
701
- raw += char;
702
- index += 1;
703
- }
704
- result += "``";
705
- };
706
- while (index < code.length) {
707
- const char = code[index];
708
- const next = code[index + 1];
709
- if (char === "/" && next === "*") {
710
- index += 2;
711
- while (index < code.length) {
712
- if (code[index] === "*" && code[index + 1] === "/") {
713
- index += 2;
714
- break;
715
- }
716
- index += 1;
717
- }
718
- continue;
719
- }
720
- if (char === "/" && next === "/") {
721
- index += 2;
722
- while (index < code.length && code[index] !== "\n") index += 1;
723
- continue;
724
- }
725
- if (char === "'" || char === "\"") {
726
- skipQuotedString(char);
727
- continue;
728
- }
729
- if (char === "`") {
730
- stripTemplateLiteral();
731
- continue;
732
- }
733
- result += char;
734
- index += 1;
735
- }
736
- return result;
737
- }
738
- function getDeliveryAliasName(node) {
739
- if (node.type === "Identifier") return node.name;
740
- if (node.type === "AssignmentPattern" && node.left.type === "Identifier") return node.left.name;
741
- return null;
742
- }
743
- function buildContextExpressionPattern(contextName) {
744
- const name = escapeRegExp(contextName);
745
- const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
746
- return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
747
- }
748
- const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
749
- if (isFunction(expr)) return expr;
750
- if (expr.type === "Identifier") {
751
- if (seen.has(expr.name)) return null;
752
- seen.add(expr.name);
753
- const binding = bindings.get(expr.name);
754
- if (binding == null || !isNode(binding)) return null;
755
- if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
756
- if (binding.type === "Identifier") return resolveListenerReference(binding, bindings, seen);
757
- return null;
758
- }
759
- if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
760
- const binding = bindings.get(expr.object.name);
761
- if (binding == null || !isNode(binding) || binding.type !== "ObjectExpression") return null;
762
- const propertyName = getMemberPropertyName(expr);
763
- if (propertyName == null) return null;
764
- for (const prop of binding.properties) {
765
- if (!isNode(prop) || prop.type !== "Property") continue;
766
- 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;
767
- const value = prop.value;
768
- if (isFunction(value) || value.type === "FunctionDeclaration") return value;
769
- }
770
- }
771
- return null;
772
- };
773
- const listenerCallsDeliveryMethod = (sourceCode, listener) => {
774
- const code = stripCommentsAndStrings(sourceCode.getText(listener));
775
- const aliases = /* @__PURE__ */ new Set();
776
- const contextParam = unwrapContextParam(listener.params[0]);
777
- const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
778
- if (contextParam?.type === "ObjectPattern") for (const prop of contextParam.properties) {
779
- if (!isNode(prop) || prop.type !== "Property") continue;
780
- const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null;
781
- if (keyName == null || !DELIVERY_METHOD_NAMES.has(keyName)) continue;
782
- const alias = getDeliveryAliasName(prop.value);
783
- if (alias != null) aliases.add(alias);
784
- }
785
- if (contextName != null) {
786
- const contextExpr = buildContextExpressionPattern(contextName);
787
- if (new RegExp(String.raw`${contextExpr}\s*(?:\?\s*\.\s*(?:sendActivity|forwardActivity)|\.\s*(?:sendActivity|forwardActivity)|\?\s*\.\s*\[\s*["'\`](?:sendActivity|forwardActivity)["'\`]\s*\]|\[\s*["'\`](?:sendActivity|forwardActivity)["'\`]\s*\])\s*\(`).test(code)) return true;
788
- const destructuringPattern = new RegExp(String.raw`(?:const|let|var)\s*{([^}]*)}\s*=\s*${contextExpr}`, "g");
789
- for (const match of code.matchAll(destructuringPattern)) {
790
- const fields = match[1].split(",").map((field) => field.trim()).filter(Boolean);
791
- for (const field of fields) {
792
- const [sourceName, aliasName] = field.split(":").map((part) => part.trim());
793
- if (!DELIVERY_METHOD_NAMES.has(sourceName)) continue;
794
- aliases.add(aliasName ?? sourceName);
795
- }
796
- }
797
- const aliasPattern = new RegExp(String.raw`(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*${contextExpr}\s*(?:\?\s*\.\s*(sendActivity|forwardActivity)|\.\s*(sendActivity|forwardActivity)|\?\s*\.\s*\[\s*["'\`](sendActivity|forwardActivity)["'\`]\s*\]|\[\s*["'\`](sendActivity|forwardActivity)["'\`]\s*\])`, "g");
798
- for (const match of code.matchAll(aliasPattern)) aliases.add(match[1]);
799
- }
800
- return globalThis.Array.from(aliases).some((alias) => new RegExp(String.raw`\b${escapeRegExp(alias)}\s*\(`).test(code));
801
- };
802
- function createRule(buildReport) {
803
- return (context) => {
804
- const federationTracker = trackFederationVariables();
805
- const bindings = /* @__PURE__ */ new Map();
806
- const pendingCalls = [];
807
- const sourceCode = context.sourceCode;
808
- const inspectCall = (node) => {
809
- if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
810
- if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
811
- const listener = node.arguments[1];
812
- const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveListenerReference(listener, bindings) : null;
813
- if (resolvedListener == null) return;
814
- if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
815
- context.report({
816
- node: resolvedListener,
817
- ...buildReport
818
- });
819
- };
820
- return {
821
- VariableDeclarator(node) {
822
- federationTracker.VariableDeclarator(node);
823
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
824
- },
825
- FunctionDeclaration(node) {
826
- if (node.id != null) bindings.set(node.id.name, node);
827
- },
828
- CallExpression(node) {
829
- pendingCalls.push(node);
830
- },
831
- "Program:exit"() {
832
- for (const node of pendingCalls) inspectCall(node);
833
- }
834
- };
835
- };
836
- }
837
- createRule({ message: MESSAGE });
838
- const eslint = {
839
- meta: {
840
- type: "suggestion",
841
- docs: { description: "Warn when an outbox listener omits explicit delivery methods" },
842
- schema: [],
843
- messages: { required: "{{ message }}" }
844
- },
845
- create: createRule({
846
- messageId: "required",
847
- data: { message: MESSAGE }
848
- })
849
- };
850
- //#endregion
851
596
  //#region src/index.ts
852
597
  /**
853
598
  * ESLint plugin for Fedify.
854
599
  * Provides lint rules for validating Fedify federation code.
855
600
  */
856
601
  const rules = {
857
- [RULE_IDS.actorIdMismatch]: eslint$12,
858
- [RULE_IDS.actorIdRequired]: eslint$11,
859
- [RULE_IDS.actorFollowingPropertyRequired]: eslint$13,
860
- [RULE_IDS.actorFollowingPropertyMismatch]: eslint$14,
861
- [RULE_IDS.actorFollowersPropertyRequired]: eslint$15,
862
- [RULE_IDS.actorFollowersPropertyMismatch]: eslint$16,
863
- [RULE_IDS.actorOutboxPropertyRequired]: eslint$5,
864
- [RULE_IDS.actorOutboxPropertyMismatch]: eslint$6,
865
- [RULE_IDS.actorLikedPropertyRequired]: eslint$7,
866
- [RULE_IDS.actorLikedPropertyMismatch]: eslint$8,
867
- [RULE_IDS.actorFeaturedPropertyRequired]: eslint$19,
868
- [RULE_IDS.actorFeaturedPropertyMismatch]: eslint$20,
869
- [RULE_IDS.actorFeaturedTagsPropertyRequired]: eslint$17,
870
- [RULE_IDS.actorFeaturedTagsPropertyMismatch]: eslint$18,
871
- [RULE_IDS.actorInboxPropertyRequired]: eslint$9,
872
- [RULE_IDS.actorInboxPropertyMismatch]: eslint$10,
873
- [RULE_IDS.actorSharedInboxPropertyRequired]: eslint$2,
874
- [RULE_IDS.actorSharedInboxPropertyMismatch]: eslint$3,
875
- [RULE_IDS.actorPublicKeyRequired]: eslint$4,
876
- [RULE_IDS.actorAssertionMethodRequired]: eslint$21,
877
- [RULE_IDS.collectionFilteringNotImplemented]: eslint$1,
878
- [RULE_IDS.outboxListenerDeliveryRequired]: eslint
602
+ [RULE_IDS.actorIdMismatch]: eslint$11,
603
+ [RULE_IDS.actorIdRequired]: eslint$10,
604
+ [RULE_IDS.actorFollowingPropertyRequired]: eslint$12,
605
+ [RULE_IDS.actorFollowingPropertyMismatch]: eslint$13,
606
+ [RULE_IDS.actorFollowersPropertyRequired]: eslint$14,
607
+ [RULE_IDS.actorFollowersPropertyMismatch]: eslint$15,
608
+ [RULE_IDS.actorOutboxPropertyRequired]: eslint$4,
609
+ [RULE_IDS.actorOutboxPropertyMismatch]: eslint$5,
610
+ [RULE_IDS.actorLikedPropertyRequired]: eslint$6,
611
+ [RULE_IDS.actorLikedPropertyMismatch]: eslint$7,
612
+ [RULE_IDS.actorFeaturedPropertyRequired]: eslint$18,
613
+ [RULE_IDS.actorFeaturedPropertyMismatch]: eslint$19,
614
+ [RULE_IDS.actorFeaturedTagsPropertyRequired]: eslint$16,
615
+ [RULE_IDS.actorFeaturedTagsPropertyMismatch]: eslint$17,
616
+ [RULE_IDS.actorInboxPropertyRequired]: eslint$8,
617
+ [RULE_IDS.actorInboxPropertyMismatch]: eslint$9,
618
+ [RULE_IDS.actorSharedInboxPropertyRequired]: eslint$1,
619
+ [RULE_IDS.actorSharedInboxPropertyMismatch]: eslint$2,
620
+ [RULE_IDS.actorPublicKeyRequired]: eslint$3,
621
+ [RULE_IDS.actorAssertionMethodRequired]: eslint$20,
622
+ [RULE_IDS.collectionFilteringNotImplemented]: eslint
879
623
  };
880
624
  const recommendedRuleIds = [RULE_IDS.actorIdMismatch, RULE_IDS.actorIdRequired];
881
625
  /**
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { always, every, fromEntries, head, isEmpty, isObject, keys, map, negate,
2
2
  import parser from "@typescript-eslint/parser";
3
3
  //#region deno.json
4
4
  var name = "@fedify/lint";
5
- var version = "2.2.0-dev.844+6cc39f0b";
5
+ var version = "2.2.0-dev.851+223698da";
6
6
  //#endregion
7
7
  //#region src/lib/const.ts
8
8
  /**
@@ -119,8 +119,7 @@ const RULE_IDS = {
119
119
  actorFeaturedTagsPropertyMismatch: "actor-featured-tags-property-mismatch",
120
120
  actorInboxPropertyMismatch: "actor-inbox-property-mismatch",
121
121
  actorSharedInboxPropertyMismatch: "actor-shared-inbox-property-mismatch",
122
- collectionFilteringNotImplemented: "collection-filtering-not-implemented",
123
- outboxListenerDeliveryRequired: "outbox-listener-delivery-required"
122
+ collectionFilteringNotImplemented: "collection-filtering-not-implemented"
124
123
  };
125
124
  //#endregion
126
125
  //#region src/lib/messages.ts
@@ -165,7 +164,6 @@ function allOf(...predicates) {
165
164
  return (value) => predicates.every((predicate) => predicate(value));
166
165
  }
167
166
  const anyOf = (...predicates) => (value) => predicates.some((predicate) => predicate(value));
168
- const isNode = (obj) => isObject(obj) && "type" in obj;
169
167
  /**
170
168
  * Checks if a node is of a specific type.
171
169
  */
@@ -418,7 +416,7 @@ function createRequiredRuleEslint(config) {
418
416
  };
419
417
  }
420
418
  createRequiredRuleDeno(properties.assertionMethod);
421
- const eslint$21 = createRequiredRuleEslint(properties.assertionMethod);
419
+ const eslint$20 = createRequiredRuleEslint(properties.assertionMethod);
422
420
  //#endregion
423
421
  //#region src/lib/mismatch.ts
424
422
  const isIdentifierWithName = (name) => (node) => allOf(isNodeType("Identifier"), isNodeName(name))(node);
@@ -488,43 +486,43 @@ const createMismatchRuleEslint = (config) => ({
488
486
  }))
489
487
  });
490
488
  createMismatchRuleDeno(properties.featured);
491
- const eslint$20 = createMismatchRuleEslint(properties.featured);
489
+ const eslint$19 = createMismatchRuleEslint(properties.featured);
492
490
  createRequiredRuleDeno(properties.featured);
493
- const eslint$19 = createRequiredRuleEslint(properties.featured);
491
+ const eslint$18 = createRequiredRuleEslint(properties.featured);
494
492
  createMismatchRuleDeno(properties.featuredTags);
495
- const eslint$18 = createMismatchRuleEslint(properties.featuredTags);
493
+ const eslint$17 = createMismatchRuleEslint(properties.featuredTags);
496
494
  createRequiredRuleDeno(properties.featuredTags);
497
- const eslint$17 = createRequiredRuleEslint(properties.featuredTags);
495
+ const eslint$16 = createRequiredRuleEslint(properties.featuredTags);
498
496
  createMismatchRuleDeno(properties.followers);
499
- const eslint$16 = createMismatchRuleEslint(properties.followers);
497
+ const eslint$15 = createMismatchRuleEslint(properties.followers);
500
498
  createRequiredRuleDeno(properties.followers);
501
- const eslint$15 = createRequiredRuleEslint(properties.followers);
499
+ const eslint$14 = createRequiredRuleEslint(properties.followers);
502
500
  createMismatchRuleDeno(properties.following);
503
- const eslint$14 = createMismatchRuleEslint(properties.following);
501
+ const eslint$13 = createMismatchRuleEslint(properties.following);
504
502
  createRequiredRuleDeno(properties.following);
505
- const eslint$13 = createRequiredRuleEslint(properties.following);
503
+ const eslint$12 = createRequiredRuleEslint(properties.following);
506
504
  createMismatchRuleDeno(properties.id);
507
- const eslint$12 = createMismatchRuleEslint(properties.id);
505
+ const eslint$11 = createMismatchRuleEslint(properties.id);
508
506
  createRequiredRuleDeno(properties.id);
509
- const eslint$11 = createRequiredRuleEslint(properties.id);
507
+ const eslint$10 = createRequiredRuleEslint(properties.id);
510
508
  createMismatchRuleDeno(properties.inbox);
511
- const eslint$10 = createMismatchRuleEslint(properties.inbox);
509
+ const eslint$9 = createMismatchRuleEslint(properties.inbox);
512
510
  createRequiredRuleDeno(properties.inbox);
513
- const eslint$9 = createRequiredRuleEslint(properties.inbox);
511
+ const eslint$8 = createRequiredRuleEslint(properties.inbox);
514
512
  createMismatchRuleDeno(properties.liked);
515
- const eslint$8 = createMismatchRuleEslint(properties.liked);
513
+ const eslint$7 = createMismatchRuleEslint(properties.liked);
516
514
  createRequiredRuleDeno(properties.liked);
517
- const eslint$7 = createRequiredRuleEslint(properties.liked);
515
+ const eslint$6 = createRequiredRuleEslint(properties.liked);
518
516
  createMismatchRuleDeno(properties.outbox);
519
- const eslint$6 = createMismatchRuleEslint(properties.outbox);
517
+ const eslint$5 = createMismatchRuleEslint(properties.outbox);
520
518
  createRequiredRuleDeno(properties.outbox);
521
- const eslint$5 = createRequiredRuleEslint(properties.outbox);
519
+ const eslint$4 = createRequiredRuleEslint(properties.outbox);
522
520
  createRequiredRuleDeno(properties.publicKey);
523
- const eslint$4 = createRequiredRuleEslint(properties.publicKey);
521
+ const eslint$3 = createRequiredRuleEslint(properties.publicKey);
524
522
  createMismatchRuleDeno(properties.sharedInbox);
525
- const eslint$3 = createMismatchRuleEslint(properties.sharedInbox);
523
+ const eslint$2 = createMismatchRuleEslint(properties.sharedInbox);
526
524
  createRequiredRuleDeno(properties.sharedInbox);
527
- const eslint$2 = createRequiredRuleEslint(properties.sharedInbox);
525
+ const eslint$1 = createRequiredRuleEslint(properties.sharedInbox);
528
526
  //#endregion
529
527
  //#region src/rules/collection-filtering-not-implemented.ts
530
528
  /**
@@ -543,7 +541,7 @@ const isFollowersDispatcherCall = (node) => "callee" in node && node.callee && n
543
541
  * CollectionDispatcher signature: (context, identifier, cursor, filter?) => ...
544
542
  */
545
543
  const hasFilterParameter = hasMinParams(4);
546
- const eslint$1 = {
544
+ const eslint = {
547
545
  meta: {
548
546
  type: "suggestion",
549
547
  docs: { description: "Ensure followers dispatcher implements filtering" },
@@ -568,287 +566,33 @@ const eslint$1 = {
568
566
  }
569
567
  };
570
568
  //#endregion
571
- //#region src/rules/outbox-listener-delivery-required.ts
572
- const MESSAGE = "Outbox listeners should deliver posted activities explicitly with ctx.sendActivity() or ctx.forwardActivity().";
573
- const isChainedFromOutboxListeners = (expr, federationTracker) => {
574
- if (expr.type !== "CallExpression") return false;
575
- if (!hasMemberExpressionCallee(expr) || !hasIdentifierProperty(expr)) return false;
576
- const methodName = expr.callee.property.name;
577
- if (methodName === "setOutboxListeners") return federationTracker.isFederationObject(expr.callee.object);
578
- if (methodName === "authorize" || methodName === "onError" || methodName === "on") return isChainedFromOutboxListeners(expr.callee.object, federationTracker);
579
- return false;
580
- };
581
- const DELIVERY_METHOD_NAMES = new Set(["sendActivity", "forwardActivity"]);
582
- const getMemberPropertyName = (expr) => {
583
- if (expr.type !== "MemberExpression") return null;
584
- const property = expr.property;
585
- if (property.type === "Identifier") return property.name;
586
- if (property.type === "Literal" && typeof property.value === "string") return property.value;
587
- return null;
588
- };
589
- function unwrapContextParam(node) {
590
- let current = node ?? null;
591
- while (current?.type === "AssignmentPattern") current = current.left;
592
- return current;
593
- }
594
- function escapeRegExp(value) {
595
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
596
- }
597
- function stripCommentsAndStrings(code) {
598
- let result = "";
599
- let index = 0;
600
- const skipQuotedString = (quote) => {
601
- const start = index;
602
- index += 1;
603
- while (index < code.length) {
604
- const char = code[index];
605
- if (char === "\\") {
606
- index += 2;
607
- continue;
608
- }
609
- index += 1;
610
- if (char === quote) break;
611
- }
612
- const literal = code.slice(start, index);
613
- const value = literal.slice(1, -1);
614
- result += DELIVERY_METHOD_NAMES.has(value) ? literal : `${quote}${quote}`;
615
- };
616
- const stripTemplateLiteral = () => {
617
- const start = index;
618
- index += 1;
619
- let raw = "";
620
- let hasExpression = false;
621
- while (index < code.length) {
622
- const char = code[index];
623
- if (char === "\\") {
624
- raw += char;
625
- raw += code[index + 1] ?? "";
626
- index += 2;
627
- continue;
628
- }
629
- if (char === "`") {
630
- index += 1;
631
- if (!hasExpression && DELIVERY_METHOD_NAMES.has(raw)) result += code.slice(start, index);
632
- else result += "``";
633
- return;
634
- }
635
- if (char === "$" && code[index + 1] === "{") {
636
- hasExpression = true;
637
- result += "`${";
638
- index += 2;
639
- let depth = 1;
640
- while (index < code.length && depth > 0) {
641
- const exprChar = code[index];
642
- const next = code[index + 1];
643
- if (exprChar === "'" || exprChar === "\"") {
644
- skipQuotedString(exprChar);
645
- continue;
646
- }
647
- if (exprChar === "`") {
648
- stripTemplateLiteral();
649
- continue;
650
- }
651
- if (exprChar === "/" && next === "*") {
652
- index += 2;
653
- while (index < code.length) {
654
- if (code[index] === "*" && code[index + 1] === "/") {
655
- index += 2;
656
- break;
657
- }
658
- index += 1;
659
- }
660
- continue;
661
- }
662
- if (exprChar === "/" && next === "/") {
663
- index += 2;
664
- while (index < code.length && code[index] !== "\n") index += 1;
665
- continue;
666
- }
667
- result += exprChar;
668
- index += 1;
669
- if (exprChar === "{") depth += 1;
670
- else if (exprChar === "}") depth -= 1;
671
- }
672
- continue;
673
- }
674
- raw += char;
675
- index += 1;
676
- }
677
- result += "``";
678
- };
679
- while (index < code.length) {
680
- const char = code[index];
681
- const next = code[index + 1];
682
- if (char === "/" && next === "*") {
683
- index += 2;
684
- while (index < code.length) {
685
- if (code[index] === "*" && code[index + 1] === "/") {
686
- index += 2;
687
- break;
688
- }
689
- index += 1;
690
- }
691
- continue;
692
- }
693
- if (char === "/" && next === "/") {
694
- index += 2;
695
- while (index < code.length && code[index] !== "\n") index += 1;
696
- continue;
697
- }
698
- if (char === "'" || char === "\"") {
699
- skipQuotedString(char);
700
- continue;
701
- }
702
- if (char === "`") {
703
- stripTemplateLiteral();
704
- continue;
705
- }
706
- result += char;
707
- index += 1;
708
- }
709
- return result;
710
- }
711
- function getDeliveryAliasName(node) {
712
- if (node.type === "Identifier") return node.name;
713
- if (node.type === "AssignmentPattern" && node.left.type === "Identifier") return node.left.name;
714
- return null;
715
- }
716
- function buildContextExpressionPattern(contextName) {
717
- const name = escapeRegExp(contextName);
718
- const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
719
- return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
720
- }
721
- const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
722
- if (isFunction(expr)) return expr;
723
- if (expr.type === "Identifier") {
724
- if (seen.has(expr.name)) return null;
725
- seen.add(expr.name);
726
- const binding = bindings.get(expr.name);
727
- if (binding == null || !isNode(binding)) return null;
728
- if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
729
- if (binding.type === "Identifier") return resolveListenerReference(binding, bindings, seen);
730
- return null;
731
- }
732
- if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
733
- const binding = bindings.get(expr.object.name);
734
- if (binding == null || !isNode(binding) || binding.type !== "ObjectExpression") return null;
735
- const propertyName = getMemberPropertyName(expr);
736
- if (propertyName == null) return null;
737
- for (const prop of binding.properties) {
738
- if (!isNode(prop) || prop.type !== "Property") continue;
739
- 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;
740
- const value = prop.value;
741
- if (isFunction(value) || value.type === "FunctionDeclaration") return value;
742
- }
743
- }
744
- return null;
745
- };
746
- const listenerCallsDeliveryMethod = (sourceCode, listener) => {
747
- const code = stripCommentsAndStrings(sourceCode.getText(listener));
748
- const aliases = /* @__PURE__ */ new Set();
749
- const contextParam = unwrapContextParam(listener.params[0]);
750
- const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
751
- if (contextParam?.type === "ObjectPattern") for (const prop of contextParam.properties) {
752
- if (!isNode(prop) || prop.type !== "Property") continue;
753
- const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null;
754
- if (keyName == null || !DELIVERY_METHOD_NAMES.has(keyName)) continue;
755
- const alias = getDeliveryAliasName(prop.value);
756
- if (alias != null) aliases.add(alias);
757
- }
758
- if (contextName != null) {
759
- const contextExpr = buildContextExpressionPattern(contextName);
760
- if (new RegExp(String.raw`${contextExpr}\s*(?:\?\s*\.\s*(?:sendActivity|forwardActivity)|\.\s*(?:sendActivity|forwardActivity)|\?\s*\.\s*\[\s*["'\`](?:sendActivity|forwardActivity)["'\`]\s*\]|\[\s*["'\`](?:sendActivity|forwardActivity)["'\`]\s*\])\s*\(`).test(code)) return true;
761
- const destructuringPattern = new RegExp(String.raw`(?:const|let|var)\s*{([^}]*)}\s*=\s*${contextExpr}`, "g");
762
- for (const match of code.matchAll(destructuringPattern)) {
763
- const fields = match[1].split(",").map((field) => field.trim()).filter(Boolean);
764
- for (const field of fields) {
765
- const [sourceName, aliasName] = field.split(":").map((part) => part.trim());
766
- if (!DELIVERY_METHOD_NAMES.has(sourceName)) continue;
767
- aliases.add(aliasName ?? sourceName);
768
- }
769
- }
770
- const aliasPattern = new RegExp(String.raw`(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*${contextExpr}\s*(?:\?\s*\.\s*(sendActivity|forwardActivity)|\.\s*(sendActivity|forwardActivity)|\?\s*\.\s*\[\s*["'\`](sendActivity|forwardActivity)["'\`]\s*\]|\[\s*["'\`](sendActivity|forwardActivity)["'\`]\s*\])`, "g");
771
- for (const match of code.matchAll(aliasPattern)) aliases.add(match[1]);
772
- }
773
- return globalThis.Array.from(aliases).some((alias) => new RegExp(String.raw`\b${escapeRegExp(alias)}\s*\(`).test(code));
774
- };
775
- function createRule(buildReport) {
776
- return (context) => {
777
- const federationTracker = trackFederationVariables();
778
- const bindings = /* @__PURE__ */ new Map();
779
- const pendingCalls = [];
780
- const sourceCode = context.sourceCode;
781
- const inspectCall = (node) => {
782
- if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
783
- if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
784
- const listener = node.arguments[1];
785
- const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveListenerReference(listener, bindings) : null;
786
- if (resolvedListener == null) return;
787
- if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
788
- context.report({
789
- node: resolvedListener,
790
- ...buildReport
791
- });
792
- };
793
- return {
794
- VariableDeclarator(node) {
795
- federationTracker.VariableDeclarator(node);
796
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
797
- },
798
- FunctionDeclaration(node) {
799
- if (node.id != null) bindings.set(node.id.name, node);
800
- },
801
- CallExpression(node) {
802
- pendingCalls.push(node);
803
- },
804
- "Program:exit"() {
805
- for (const node of pendingCalls) inspectCall(node);
806
- }
807
- };
808
- };
809
- }
810
- createRule({ message: MESSAGE });
811
- const eslint = {
812
- meta: {
813
- type: "suggestion",
814
- docs: { description: "Warn when an outbox listener omits explicit delivery methods" },
815
- schema: [],
816
- messages: { required: "{{ message }}" }
817
- },
818
- create: createRule({
819
- messageId: "required",
820
- data: { message: MESSAGE }
821
- })
822
- };
823
- //#endregion
824
569
  //#region src/index.ts
825
570
  /**
826
571
  * ESLint plugin for Fedify.
827
572
  * Provides lint rules for validating Fedify federation code.
828
573
  */
829
574
  const rules = {
830
- [RULE_IDS.actorIdMismatch]: eslint$12,
831
- [RULE_IDS.actorIdRequired]: eslint$11,
832
- [RULE_IDS.actorFollowingPropertyRequired]: eslint$13,
833
- [RULE_IDS.actorFollowingPropertyMismatch]: eslint$14,
834
- [RULE_IDS.actorFollowersPropertyRequired]: eslint$15,
835
- [RULE_IDS.actorFollowersPropertyMismatch]: eslint$16,
836
- [RULE_IDS.actorOutboxPropertyRequired]: eslint$5,
837
- [RULE_IDS.actorOutboxPropertyMismatch]: eslint$6,
838
- [RULE_IDS.actorLikedPropertyRequired]: eslint$7,
839
- [RULE_IDS.actorLikedPropertyMismatch]: eslint$8,
840
- [RULE_IDS.actorFeaturedPropertyRequired]: eslint$19,
841
- [RULE_IDS.actorFeaturedPropertyMismatch]: eslint$20,
842
- [RULE_IDS.actorFeaturedTagsPropertyRequired]: eslint$17,
843
- [RULE_IDS.actorFeaturedTagsPropertyMismatch]: eslint$18,
844
- [RULE_IDS.actorInboxPropertyRequired]: eslint$9,
845
- [RULE_IDS.actorInboxPropertyMismatch]: eslint$10,
846
- [RULE_IDS.actorSharedInboxPropertyRequired]: eslint$2,
847
- [RULE_IDS.actorSharedInboxPropertyMismatch]: eslint$3,
848
- [RULE_IDS.actorPublicKeyRequired]: eslint$4,
849
- [RULE_IDS.actorAssertionMethodRequired]: eslint$21,
850
- [RULE_IDS.collectionFilteringNotImplemented]: eslint$1,
851
- [RULE_IDS.outboxListenerDeliveryRequired]: eslint
575
+ [RULE_IDS.actorIdMismatch]: eslint$11,
576
+ [RULE_IDS.actorIdRequired]: eslint$10,
577
+ [RULE_IDS.actorFollowingPropertyRequired]: eslint$12,
578
+ [RULE_IDS.actorFollowingPropertyMismatch]: eslint$13,
579
+ [RULE_IDS.actorFollowersPropertyRequired]: eslint$14,
580
+ [RULE_IDS.actorFollowersPropertyMismatch]: eslint$15,
581
+ [RULE_IDS.actorOutboxPropertyRequired]: eslint$4,
582
+ [RULE_IDS.actorOutboxPropertyMismatch]: eslint$5,
583
+ [RULE_IDS.actorLikedPropertyRequired]: eslint$6,
584
+ [RULE_IDS.actorLikedPropertyMismatch]: eslint$7,
585
+ [RULE_IDS.actorFeaturedPropertyRequired]: eslint$18,
586
+ [RULE_IDS.actorFeaturedPropertyMismatch]: eslint$19,
587
+ [RULE_IDS.actorFeaturedTagsPropertyRequired]: eslint$16,
588
+ [RULE_IDS.actorFeaturedTagsPropertyMismatch]: eslint$17,
589
+ [RULE_IDS.actorInboxPropertyRequired]: eslint$8,
590
+ [RULE_IDS.actorInboxPropertyMismatch]: eslint$9,
591
+ [RULE_IDS.actorSharedInboxPropertyRequired]: eslint$1,
592
+ [RULE_IDS.actorSharedInboxPropertyMismatch]: eslint$2,
593
+ [RULE_IDS.actorPublicKeyRequired]: eslint$3,
594
+ [RULE_IDS.actorAssertionMethodRequired]: eslint$20,
595
+ [RULE_IDS.collectionFilteringNotImplemented]: eslint
852
596
  };
853
597
  const recommendedRuleIds = [RULE_IDS.actorIdMismatch, RULE_IDS.actorIdRequired];
854
598
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/lint",
3
- "version": "2.2.0-dev.844+6cc39f0b",
3
+ "version": "2.2.0-dev.851+223698da",
4
4
  "description": "Fedify linting rules and plugins",
5
5
  "keywords": [
6
6
  "Fedify",
@@ -47,7 +47,7 @@
47
47
  ],
48
48
  "peerDependencies": {
49
49
  "eslint": ">=9.0.0",
50
- "@fedify/fedify": "^2.2.0-dev.844+6cc39f0b"
50
+ "@fedify/fedify": "^2.2.0-dev.851+223698da"
51
51
  },
52
52
  "peerDependenciesMeta": {
53
53
  "eslint": {