@fedify/lint 2.3.0-pr.809.39 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -24,865 +24,46 @@ 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-DWbQtz5p.cjs");
27
28
  let _fxts_core = require("@fxts/core");
28
29
  let _typescript_eslint_parser = require("@typescript-eslint/parser");
29
30
  _typescript_eslint_parser = __toESM(_typescript_eslint_parser, 1);
30
- //#region deno.json
31
- var name = "@fedify/lint";
32
- var version = "2.3.0-pr.809.39+f65f60c0";
33
- //#endregion
34
- //#region src/lib/const.ts
35
- /**
36
- * Mapping of actor property names to their corresponding Context method names
37
- * and dispatcher methods.
38
- * Used by lint rules to validate property existence and correct method usage.
39
- */
40
- const properties = {
41
- id: {
42
- name: "id",
43
- path: ["id"],
44
- getter: "getActorUri",
45
- setter: "setActorDispatcher",
46
- requiresIdentifier: true
47
- },
48
- following: {
49
- name: "following",
50
- path: ["following"],
51
- getter: "getFollowingUri",
52
- setter: "setFollowingDispatcher",
53
- requiresIdentifier: true
54
- },
55
- followers: {
56
- name: "followers",
57
- path: ["followers"],
58
- getter: "getFollowersUri",
59
- setter: "setFollowersDispatcher",
60
- requiresIdentifier: true
61
- },
62
- outbox: {
63
- name: "outbox",
64
- path: ["outbox"],
65
- getter: "getOutboxUri",
66
- setter: "setOutboxDispatcher",
67
- requiresIdentifier: true
68
- },
69
- inbox: {
70
- name: "inbox",
71
- path: ["inbox"],
72
- getter: "getInboxUri",
73
- setter: "setInboxListeners",
74
- requiresIdentifier: true
75
- },
76
- liked: {
77
- name: "liked",
78
- path: ["liked"],
79
- getter: "getLikedUri",
80
- setter: "setLikedDispatcher",
81
- requiresIdentifier: true
82
- },
83
- featured: {
84
- name: "featured",
85
- path: ["featured"],
86
- getter: "getFeaturedUri",
87
- setter: "setFeaturedDispatcher",
88
- requiresIdentifier: true
89
- },
90
- featuredTags: {
91
- name: "featuredTags",
92
- path: ["featuredTags"],
93
- getter: "getFeaturedTagsUri",
94
- setter: "setFeaturedTagsDispatcher",
95
- requiresIdentifier: true
96
- },
97
- sharedInbox: {
98
- name: "sharedInbox",
99
- path: ["endpoints", "sharedInbox"],
100
- getter: "getInboxUri",
101
- setter: "setInboxListeners",
102
- requiresIdentifier: false,
103
- nested: {
104
- parent: "endpoints",
105
- wrapper: "Endpoints"
106
- }
107
- },
108
- publicKey: {
109
- name: "publicKey",
110
- path: ["publicKey"],
111
- getter: "getActorKeyPairs",
112
- setter: "setKeyPairsDispatcher",
113
- requiresIdentifier: true,
114
- isKeyProperty: true
115
- },
116
- assertionMethod: {
117
- name: "assertionMethod",
118
- path: ["assertionMethod"],
119
- getter: "getActorKeyPairs",
120
- setter: "setKeyPairsDispatcher",
121
- requiresIdentifier: true,
122
- isKeyProperty: true
123
- }
124
- };
125
- /**
126
- * Rule IDs for all Fedify lint rules
127
- */
128
- const RULE_IDS = {
129
- actorIdRequired: "actor-id-required",
130
- actorFollowingPropertyRequired: "actor-following-property-required",
131
- actorFollowersPropertyRequired: "actor-followers-property-required",
132
- actorOutboxPropertyRequired: "actor-outbox-property-required",
133
- actorLikedPropertyRequired: "actor-liked-property-required",
134
- actorFeaturedPropertyRequired: "actor-featured-property-required",
135
- actorFeaturedTagsPropertyRequired: "actor-featured-tags-property-required",
136
- actorInboxPropertyRequired: "actor-inbox-property-required",
137
- actorSharedInboxPropertyRequired: "actor-shared-inbox-property-required",
138
- actorPublicKeyRequired: "actor-public-key-required",
139
- actorAssertionMethodRequired: "actor-assertion-method-required",
140
- actorIdMismatch: "actor-id-mismatch",
141
- actorFollowingPropertyMismatch: "actor-following-property-mismatch",
142
- actorFollowersPropertyMismatch: "actor-followers-property-mismatch",
143
- actorOutboxPropertyMismatch: "actor-outbox-property-mismatch",
144
- actorLikedPropertyMismatch: "actor-liked-property-mismatch",
145
- actorFeaturedPropertyMismatch: "actor-featured-property-mismatch",
146
- actorFeaturedTagsPropertyMismatch: "actor-featured-tags-property-mismatch",
147
- actorInboxPropertyMismatch: "actor-inbox-property-mismatch",
148
- actorSharedInboxPropertyMismatch: "actor-shared-inbox-property-mismatch",
149
- collectionFilteringNotImplemented: "collection-filtering-not-implemented",
150
- outboxListenerDeliveryRequired: "outbox-listener-delivery-required"
151
- };
152
- //#endregion
153
- //#region src/lib/messages.ts
154
- /**
155
- * Generates error message for *-required rules.
156
- * Used when a property is missing from the actor dispatcher return value.
157
- *
158
- * @param name - The property name (e.g., "id", "inbox", "following")
159
- */
160
- const actorPropertyRequired = ({ setter, path, getter, requiresIdentifier = true }) => `When \`${setter}\` is configured, the \`${path.join(".")}\` property is recommended. Use \`${getExpectedCall({
161
- ctxName: "Context",
162
- methodName: getter,
163
- idName: "identifier",
164
- path: path.join("."),
165
- requiresIdentifier
166
- })}\` for the \`${path.join(".")}\` property URI.`;
167
- /**
168
- * Generates error message for *-mismatch rules.
169
- * Used when a property exists but uses the wrong context method.
170
- *
171
- * @param propertyName - The property name or path
172
- * (e.g., "id", "endpoints.sharedInbox")
173
- * @param expectedCall - The expected method call
174
- * (e.g., "ctx.getActorUri(identifier)")
175
- */
176
- const actorPropertyMismatch = (context) => `Actor's \`${context.path}\` property must match \`${getExpectedCall(context)}\`. \
177
- Ensure you're using the correct context method.`;
178
- const getExpectedCall = ({ ctxName, methodName, requiresIdentifier, idName }) => requiresIdentifier ? `${ctxName}.${methodName}(${idName})` : `${ctxName}.${methodName}()`;
179
- /**
180
- * Error message for collection filtering not implemented.
181
- */
182
- const COLLECTION_FILTERING_NOT_IMPLEMENTED_ERROR = "Collection dispatcher should implement filtering to avoid large response payloads. Add a fourth parameter (filter) to handle filtering. See: https://fedify.dev/manual/collections#filtering-by-server";
183
- //#endregion
184
- //#region src/lib/utils.ts
185
- const eq = (value) => (other) => value === other;
186
- function cases(pred, ifTrue, ifFalse) {
187
- return (value) => pred(value) ? ifTrue(value) : ifFalse(value);
188
- }
189
- //#endregion
190
- //#region src/lib/pred.ts
191
- function allOf(...predicates) {
192
- return (value) => predicates.every((predicate) => predicate(value));
193
- }
194
- const anyOf = (...predicates) => (value) => predicates.some((predicate) => predicate(value));
195
- const isNode = (obj) => (0, _fxts_core.isObject)(obj) && "type" in obj;
196
- /**
197
- * Checks if a node is of a specific type.
198
- */
199
- const isNodeType = (type) => (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("type"), eq(type));
200
- /**
201
- * Checks if a node is of a specific name.
202
- */
203
- const isNodeName = (name) => (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("name"), eq(name));
204
- /**
205
- * Checks if a node's callee is a MemberExpression.
206
- */
207
- const hasMemberExpressionCallee = (node) => node.callee.type === "MemberExpression";
208
- /**
209
- * Checks if a node's callee property is an Identifier.
210
- */
211
- const hasIdentifierProperty = (node) => "callee" in node && "property" in node.callee && "type" in node.callee.property && node.callee.property.type === "Identifier";
212
- /**
213
- * Checks if a node's callee property name matches the given method name.
214
- */
215
- const hasMethodName = (methodName) => (node) => "callee" in node && "property" in node.callee && "name" in node.callee.property && node.callee.property.name === methodName;
216
- /**
217
- * Checks if a CallExpression has minimum required arguments.
218
- */
219
- const hasMinArguments = (min) => (node) => node.arguments.length >= min;
220
- /**
221
- * Checks if an expression is a function (arrow or regular).
222
- */
223
- const isFunction = (expr) => anyOf(isNodeType("ArrowFunctionExpression"), isNodeType("FunctionExpression"))(expr);
224
- /**
225
- * Checks if a CallExpression is a setActorDispatcher call with
226
- * proper structure.
227
- */
228
- const isSetActorDispatcherCall = (node) => allOf(hasMemberExpressionCallee, hasIdentifierProperty, hasMethodName("setActorDispatcher"), hasMinArguments(2))(node);
229
- /**
230
- * Checks if a function has at least n parameters.
231
- */
232
- const hasMinParams = (min) => (fn) => fn.params.length >= min;
233
- //#endregion
234
- //#region src/lib/property-checker.ts
235
- /**
236
- * Checks if a node has a key with a specific name.
237
- */
238
- const hasKeyName = (propertyName) => (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("key"), allOf(isNodeType("Identifier"), (0, _fxts_core.pipeLazy)((0, _fxts_core.prop)("name"), eq(propertyName))));
239
- /**
240
- * Checks if a node is a Property with an Identifier key of a specific name.
241
- */
242
- const isPropertyWithName = (propertyName) => (node) => allOf(isNodeType("Property"), hasKeyName(propertyName))(node);
243
- /**
244
- * Creates a predicate function that checks if a nested property exists.
245
- * @param path Array of property names forming the path
246
- * (e.g., ["endpoints", "sharedInbox"])
247
- * @returns A predicate function that checks if the nested property exists
248
- */
249
- function createPropertyChecker(checker) {
250
- const inner = ([first, ...rest]) => (node) => {
251
- if (!isPropertyWithName(first)(node)) return false;
252
- if ((0, _fxts_core.isEmpty)(rest)) return checker(node.value);
253
- if (isNodeType("NewExpression")(node.value)) {
254
- if (node.value.arguments.length === 0) return false;
255
- const firstArg = node.value.arguments[0];
256
- if (!isNodeType("ObjectExpression")(firstArg)) return false;
257
- return firstArg.properties.some(inner(rest));
258
- }
259
- return false;
260
- };
261
- return inner;
262
- }
263
- /**
264
- * Checks if an ObjectExpression node contains a property.
265
- * @param propertyChecker The predicate function to check properties
266
- * @returns A function that checks the ObjectExpression
267
- */
268
- const checkObjectExpression = (propertyChecker) => (obj) => obj.properties.some(propertyChecker);
269
- /**
270
- * Checks if a ConditionalExpression (ternary operator) has the property in
271
- * both branches.
272
- * @param propertyChecker The predicate function to check properties
273
- * @returns A function that checks the ConditionalExpression
274
- */
275
- const checkConditionalExpression = (propertyChecker) => (node) => [node.consequent, node.alternate].every(checkBranchWith(propertyChecker));
276
- const checkBranchWith = (propertyChecker) => (branch) => (0, _fxts_core.pipe)(branch, cases(isNodeType("ConditionalExpression"), checkConditionalExpression(propertyChecker), (0, _fxts_core.pipeLazy)(extractObjectExpression, cases(_fxts_core.isObject, checkObjectExpression(propertyChecker), (0, _fxts_core.always)(false)))));
277
- /**
278
- * Extracts the first argument if it's an ObjectExpression.
279
- */
280
- const extractFirstObjectExpression = (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("arguments"), _fxts_core.head, (0, _fxts_core.unless)(isNodeType("ObjectExpression"), (0, _fxts_core.always)(null)));
281
- /**
282
- * Extracts ObjectExpression from NewExpression.
283
- */
284
- const extractObjectExpression = cases(isNodeType("NewExpression"), extractFirstObjectExpression, (0, _fxts_core.always)(null));
285
- /**
286
- * Checks if a ReturnStatement node contains a property.
287
- * @param propertyChecker The predicate function to check properties
288
- * @returns A function that checks the ReturnStatement
289
- */
290
- const checkReturnStatement = (propertyChecker) => (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("argument"), cases(_fxts_core.isObject, checkBranchWith(propertyChecker), (0, _fxts_core.always)(false)));
291
- /**
292
- * Creates a function that recursively checks for a property in an AST node.
293
- * @param propertyChecker The predicate function to check properties
294
- * @returns A recursive function that checks the AST node
295
- */
296
- const createPropertySearcher = (propertyChecker) => {
297
- return (node) => {
298
- switch (node.type) {
299
- case "ReturnStatement": return checkReturnStatement(propertyChecker)(node);
300
- case "BlockStatement": return checkAllReturnPaths(propertyChecker)(node);
301
- case "NewExpression": return (0, _fxts_core.pipe)(node, extractFirstObjectExpression, (0, _fxts_core.when)(_fxts_core.isObject, checkObjectExpression(propertyChecker)), Boolean);
302
- default: return false;
303
- }
304
- };
305
- };
306
- const checkAllReturnPaths = (propertyChecker) => (node) => (0, _fxts_core.pipe)(node, collectReturnPaths, cases(_fxts_core.isEmpty, (0, _fxts_core.always)(false), (0, _fxts_core.every)(checkReturnStatement(propertyChecker))));
307
- /**
308
- * Collects all return statements from a node, traversing control flow.
309
- * This handles if/else branches, loops, etc.
310
- */
311
- const collectReturnPaths = (node) => (0, _fxts_core.pipe)(node, flatten, _fxts_core.toArray);
312
- function* flatten(node) {
313
- switch (node.type) {
314
- case "ReturnStatement":
315
- yield node;
316
- return;
317
- case "IfStatement":
318
- if (node.consequent) yield* flatten(node.consequent);
319
- if (node.alternate) yield* flatten(node.alternate);
320
- return;
321
- case "BlockStatement":
322
- for (const statement of node.body) yield* flatten(statement);
323
- return;
324
- case "SwitchStatement":
325
- for (const switchCase of node.cases) for (const statement of switchCase.consequent) yield* flatten(statement);
326
- return;
327
- case "TryStatement":
328
- yield* flatten(node.block);
329
- if (node.handler) yield* flatten(node.handler.body);
330
- if (node.finalizer) yield* flatten(node.finalizer);
331
- return;
332
- case "WhileStatement":
333
- case "DoWhileStatement":
334
- case "ForStatement":
335
- case "ForInStatement":
336
- case "ForOfStatement":
337
- yield* flatten(node.body);
338
- return;
339
- case "LabeledStatement":
340
- yield* flatten(node.body);
341
- return;
342
- case "WithStatement":
343
- yield* flatten(node.body);
344
- return;
345
- default: return;
346
- }
347
- }
348
- //#endregion
349
- //#region src/lib/tracker.ts
350
- /**
351
- * Helper to track variable names that store the result of createFederation() or createFederationBuilder() calls
352
- */
353
- function trackFederationVariables() {
354
- const federationVariables = /* @__PURE__ */ new Set();
355
- const isFederationObject = (obj) => {
356
- switch (obj.type) {
357
- case "Identifier": return federationVariables.has(obj.name);
358
- case "CallExpression":
359
- if (isCreateFederation(obj)) return true;
360
- if (obj.callee.type === "MemberExpression") return isFederationObject(obj.callee.object);
361
- return false;
362
- case "MemberExpression": return isFederationObject(obj.object);
363
- }
364
- return false;
365
- };
366
- return {
367
- VariableDeclarator(node) {
368
- const init = node.init;
369
- const id = node.id;
370
- if (init?.type === "CallExpression") {
371
- if (isCreateFederation(init) && id.type === "Identifier") federationVariables.add(id.name);
372
- }
373
- },
374
- isFederationVariable(name) {
375
- return federationVariables.has(name);
376
- },
377
- isFederationObject
378
- };
379
- }
380
- const isCreateFederation = (node) => node.callee.type === "Identifier" && /^create(Federation|FederationBuilder)$/i.test(node.callee.name);
381
- //#endregion
382
- //#region src/lib/required.ts
383
- /**
384
- * Checks if a CallExpression is a specific dispatcher method call.
385
- */
386
- const isDispatcherMethodCall = (methodName) => (node) => allOf(hasMemberExpressionCallee, hasIdentifierProperty, hasMethodName(methodName))(node);
387
- /**
388
- * Tracks dispatcher method calls on federation objects.
389
- */
390
- const createDispatcherTracker = (dispatcherMethod, federationTracker) => {
391
- let dispatcherConfigured = false;
392
- return {
393
- isDispatcherConfigured: () => dispatcherConfigured,
394
- checkDispatcherCall: (node) => {
395
- if (isDispatcherMethodCall(dispatcherMethod)(node) && federationTracker.isFederationObject(node.callee.object)) dispatcherConfigured = true;
396
- }
397
- };
398
- };
399
- function createRequiredRule(config, description) {
400
- return (context) => {
401
- const federationTracker = trackFederationVariables();
402
- const dispatcherTracker = createDispatcherTracker(config.setter, federationTracker);
403
- const actorDispatchers = [];
404
- const propertySearcher = createPropertySearcher(createPropertyChecker(Boolean)(config.path));
405
- return {
406
- VariableDeclarator: federationTracker.VariableDeclarator,
407
- CallExpression(node) {
408
- dispatcherTracker.checkDispatcherCall(node);
409
- if (!isSetActorDispatcherCall(node)) return;
410
- if (!federationTracker.isFederationObject(node.callee.object)) return;
411
- const dispatcher = node.arguments[1];
412
- if (isFunction(dispatcher)) actorDispatchers.push(dispatcher);
413
- },
414
- "Program:exit"() {
415
- if (!dispatcherTracker.isDispatcherConfigured()) return;
416
- for (const dispatcher of actorDispatchers) if (!propertySearcher(dispatcher.body)) context.report({
417
- node: dispatcher,
418
- ...description
419
- });
420
- }
421
- };
422
- };
423
- }
424
- /**
425
- * Creates a required rule with the given property configuration.
426
- */
427
- function createRequiredRuleDeno(config) {
428
- return { create: createRequiredRule(config, { message: actorPropertyRequired(config) }) };
429
- }
430
- /**
431
- * Creates a required ESLint rule with the given property configuration.
432
- */
433
- function createRequiredRuleEslint(config) {
434
- return {
435
- meta: {
436
- type: "suggestion",
437
- docs: { description: `Ensure actor dispatcher returns ${config.path.join(".")} property.` },
438
- schema: [],
439
- messages: { required: "{{ message }}" }
440
- },
441
- create: createRequiredRule(config, {
442
- messageId: "required",
443
- data: { message: actorPropertyRequired(config) }
444
- })
445
- };
446
- }
447
- createRequiredRuleDeno(properties.assertionMethod);
448
- const eslint$21 = createRequiredRuleEslint(properties.assertionMethod);
449
- //#endregion
450
- //#region src/lib/mismatch.ts
451
- const isIdentifierWithName = (name) => (node) => allOf(isNodeType("Identifier"), isNodeName(name))(node);
452
- /**
453
- * Checks if a node is a CallExpression calling the expected context method.
454
- */
455
- const isExpectedMethodCall = ({ ctxName, idName, methodName, requiresIdentifier }) => (node) => {
456
- if (!isNodeType("CallExpression")(node) || !hasMemberExpressionCallee(node) || !isIdentifierWithName(ctxName)(node.callee.object) || !isIdentifierWithName(methodName)(node.callee.property)) return false;
457
- if (!requiresIdentifier) return (0, _fxts_core.isEmpty)(node.arguments);
458
- return allOf((0, _fxts_core.negate)(_fxts_core.isEmpty), (0, _fxts_core.some)(isIdentifierWithName(idName)))(node.arguments);
459
- };
460
- /**
461
- * Extracts parameter names from a function.
462
- */
463
- const extractParams = (fn) => {
464
- const params = fn.params;
465
- if (params.length < 2) return [null, null];
466
- return params.slice(0, 2).map(getNameIfIdentifier);
467
- };
468
- const getNameIfIdentifier = (node) => node?.type === "Identifier" ? node.name : null;
469
- function createMismatchRule(config, describe) {
470
- return (context) => {
471
- const tracker = trackFederationVariables();
472
- return {
473
- VariableDeclarator: tracker.VariableDeclarator,
474
- CallExpression(node) {
475
- if (!isSetActorDispatcherCall(node) || !hasMemberExpressionCallee(node) || !tracker.isFederationObject(node.callee.object)) return;
476
- const dispatcherArg = node.arguments[1];
477
- if (!isFunction(dispatcherArg)) return;
478
- const [ctxName, idName] = extractParams(dispatcherArg);
479
- if (!ctxName || !idName) return;
480
- const methodCallContext = {
481
- path: config.path.join("."),
482
- ctxName,
483
- idName,
484
- methodName: config.getter,
485
- requiresIdentifier: config.requiresIdentifier
486
- };
487
- if (!createPropertySearcher(createPropertyChecker(Boolean)(config.path))(dispatcherArg.body)) return;
488
- if (!createPropertySearcher(createPropertyChecker(isExpectedMethodCall(methodCallContext))(config.path))(dispatcherArg.body)) context.report({
489
- node: dispatcherArg,
490
- ...describe(methodCallContext)
491
- });
492
- }
493
- };
494
- };
495
- }
496
- /**
497
- * Creates a lint rule that checks if a property uses
498
- * the correct context method.
499
- *
500
- * @param config Property configuration containing name, getter, setter, and
501
- * nested info
502
- * @returns A Deno lint rule
503
- */
504
- const createMismatchRuleDeno = (config) => ({ create: createMismatchRule(config, (context) => ({ message: actorPropertyMismatch(context) })) });
505
- const createMismatchRuleEslint = (config) => ({
506
- meta: {
507
- type: "problem",
508
- docs: { description: `Ensure actor's ${config.path.join(".")} property uses correct context method` },
509
- schema: [],
510
- messages: { mismatch: "{{ message }}" }
511
- },
512
- create: createMismatchRule(config, (context) => ({
513
- messageId: "mismatch",
514
- data: { message: actorPropertyMismatch(context) }
515
- }))
516
- });
517
- createMismatchRuleDeno(properties.featured);
518
- const eslint$20 = createMismatchRuleEslint(properties.featured);
519
- createRequiredRuleDeno(properties.featured);
520
- const eslint$19 = createRequiredRuleEslint(properties.featured);
521
- createMismatchRuleDeno(properties.featuredTags);
522
- const eslint$18 = createMismatchRuleEslint(properties.featuredTags);
523
- createRequiredRuleDeno(properties.featuredTags);
524
- const eslint$17 = createRequiredRuleEslint(properties.featuredTags);
525
- createMismatchRuleDeno(properties.followers);
526
- const eslint$16 = createMismatchRuleEslint(properties.followers);
527
- createRequiredRuleDeno(properties.followers);
528
- const eslint$15 = createRequiredRuleEslint(properties.followers);
529
- createMismatchRuleDeno(properties.following);
530
- const eslint$14 = createMismatchRuleEslint(properties.following);
531
- createRequiredRuleDeno(properties.following);
532
- const eslint$13 = createRequiredRuleEslint(properties.following);
533
- createMismatchRuleDeno(properties.id);
534
- const eslint$12 = createMismatchRuleEslint(properties.id);
535
- createRequiredRuleDeno(properties.id);
536
- const eslint$11 = createRequiredRuleEslint(properties.id);
537
- createMismatchRuleDeno(properties.inbox);
538
- const eslint$10 = createMismatchRuleEslint(properties.inbox);
539
- createRequiredRuleDeno(properties.inbox);
540
- const eslint$9 = createRequiredRuleEslint(properties.inbox);
541
- createMismatchRuleDeno(properties.liked);
542
- const eslint$8 = createMismatchRuleEslint(properties.liked);
543
- createRequiredRuleDeno(properties.liked);
544
- const eslint$7 = createRequiredRuleEslint(properties.liked);
545
- createMismatchRuleDeno(properties.outbox);
546
- const eslint$6 = createMismatchRuleEslint(properties.outbox);
547
- createRequiredRuleDeno(properties.outbox);
548
- const eslint$5 = createRequiredRuleEslint(properties.outbox);
549
- createRequiredRuleDeno(properties.publicKey);
550
- const eslint$4 = createRequiredRuleEslint(properties.publicKey);
551
- createMismatchRuleDeno(properties.sharedInbox);
552
- const eslint$3 = createMismatchRuleEslint(properties.sharedInbox);
553
- createRequiredRuleDeno(properties.sharedInbox);
554
- const eslint$2 = createRequiredRuleEslint(properties.sharedInbox);
555
- //#endregion
556
- //#region src/rules/collection-filtering-not-implemented.ts
557
- /**
558
- * The followers dispatcher method that supports filtering.
559
- * Only setFollowersDispatcher uses the filter parameter for
560
- * followers collection synchronization.
561
- * See: https://fedify.dev/manual/collections#filtering-by-server
562
- */
563
- const FILTER_NEEDED = ["setFollowersDispatcher"];
564
- /**
565
- * Checks if a node is a setFollowersDispatcher call.
566
- */
567
- const isFollowersDispatcherCall = (node) => "callee" in node && node.callee && node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && FILTER_NEEDED.includes(node.callee.property.name);
568
- /**
569
- * Checks if a function node has the filter parameter (4th parameter).
570
- * CollectionDispatcher signature: (context, identifier, cursor, filter?) => ...
571
- */
572
- const hasFilterParameter = hasMinParams(4);
573
- const eslint$1 = {
574
- meta: {
575
- type: "suggestion",
576
- docs: { description: "Ensure followers dispatcher implements filtering" },
577
- schema: [],
578
- messages: { filterRequired: COLLECTION_FILTERING_NOT_IMPLEMENTED_ERROR }
579
- },
580
- create(context) {
581
- const federationTracker = trackFederationVariables();
582
- return {
583
- VariableDeclarator: federationTracker.VariableDeclarator,
584
- CallExpression(node) {
585
- if (!isFollowersDispatcherCall(node)) return;
586
- if (!federationTracker.isFederationObject(node.callee.object)) return;
587
- const dispatcherArg = node.arguments[1];
588
- if (!isFunction(dispatcherArg)) return;
589
- if (!hasFilterParameter(dispatcherArg)) context.report({
590
- node: dispatcherArg,
591
- messageId: "filterRequired"
592
- });
593
- }
594
- };
595
- }
596
- };
597
- //#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
- const eslint = {
838
- meta: {
839
- type: "suggestion",
840
- docs: { description: "Warn when an outbox listener omits explicit delivery methods" },
841
- schema: [],
842
- messages: { required: "{{ message }}" }
843
- },
844
- create: createRule({
845
- messageId: "required",
846
- data: { message: MESSAGE }
847
- })
848
- };
849
- //#endregion
850
31
  //#region src/index.ts
851
32
  /**
852
33
  * ESLint plugin for Fedify.
853
34
  * Provides lint rules for validating Fedify federation code.
854
35
  */
855
36
  const rules = {
856
- [RULE_IDS.actorIdMismatch]: eslint$12,
857
- [RULE_IDS.actorIdRequired]: eslint$11,
858
- [RULE_IDS.actorFollowingPropertyRequired]: eslint$13,
859
- [RULE_IDS.actorFollowingPropertyMismatch]: eslint$14,
860
- [RULE_IDS.actorFollowersPropertyRequired]: eslint$15,
861
- [RULE_IDS.actorFollowersPropertyMismatch]: eslint$16,
862
- [RULE_IDS.actorOutboxPropertyRequired]: eslint$5,
863
- [RULE_IDS.actorOutboxPropertyMismatch]: eslint$6,
864
- [RULE_IDS.actorLikedPropertyRequired]: eslint$7,
865
- [RULE_IDS.actorLikedPropertyMismatch]: eslint$8,
866
- [RULE_IDS.actorFeaturedPropertyRequired]: eslint$19,
867
- [RULE_IDS.actorFeaturedPropertyMismatch]: eslint$20,
868
- [RULE_IDS.actorFeaturedTagsPropertyRequired]: eslint$17,
869
- [RULE_IDS.actorFeaturedTagsPropertyMismatch]: eslint$18,
870
- [RULE_IDS.actorInboxPropertyRequired]: eslint$9,
871
- [RULE_IDS.actorInboxPropertyMismatch]: eslint$10,
872
- [RULE_IDS.actorSharedInboxPropertyRequired]: eslint$2,
873
- [RULE_IDS.actorSharedInboxPropertyMismatch]: eslint$3,
874
- [RULE_IDS.actorPublicKeyRequired]: eslint$4,
875
- [RULE_IDS.actorAssertionMethodRequired]: eslint$21,
876
- [RULE_IDS.collectionFilteringNotImplemented]: eslint$1,
877
- [RULE_IDS.outboxListenerDeliveryRequired]: eslint
878
- };
879
- const recommendedRuleIds = [RULE_IDS.actorIdMismatch, RULE_IDS.actorIdRequired];
37
+ [require_outbox_listener_delivery_required.RULE_IDS.actorIdMismatch]: require_outbox_listener_delivery_required.eslint$12,
38
+ [require_outbox_listener_delivery_required.RULE_IDS.actorIdRequired]: require_outbox_listener_delivery_required.eslint$11,
39
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFollowingPropertyRequired]: require_outbox_listener_delivery_required.eslint$13,
40
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFollowingPropertyMismatch]: require_outbox_listener_delivery_required.eslint$14,
41
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFollowersPropertyRequired]: require_outbox_listener_delivery_required.eslint$15,
42
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFollowersPropertyMismatch]: require_outbox_listener_delivery_required.eslint$16,
43
+ [require_outbox_listener_delivery_required.RULE_IDS.actorOutboxPropertyRequired]: require_outbox_listener_delivery_required.eslint$5,
44
+ [require_outbox_listener_delivery_required.RULE_IDS.actorOutboxPropertyMismatch]: require_outbox_listener_delivery_required.eslint$6,
45
+ [require_outbox_listener_delivery_required.RULE_IDS.actorLikedPropertyRequired]: require_outbox_listener_delivery_required.eslint$7,
46
+ [require_outbox_listener_delivery_required.RULE_IDS.actorLikedPropertyMismatch]: require_outbox_listener_delivery_required.eslint$8,
47
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFeaturedPropertyRequired]: require_outbox_listener_delivery_required.eslint$19,
48
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFeaturedPropertyMismatch]: require_outbox_listener_delivery_required.eslint$20,
49
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFeaturedTagsPropertyRequired]: require_outbox_listener_delivery_required.eslint$17,
50
+ [require_outbox_listener_delivery_required.RULE_IDS.actorFeaturedTagsPropertyMismatch]: require_outbox_listener_delivery_required.eslint$18,
51
+ [require_outbox_listener_delivery_required.RULE_IDS.actorInboxPropertyRequired]: require_outbox_listener_delivery_required.eslint$9,
52
+ [require_outbox_listener_delivery_required.RULE_IDS.actorInboxPropertyMismatch]: require_outbox_listener_delivery_required.eslint$10,
53
+ [require_outbox_listener_delivery_required.RULE_IDS.actorSharedInboxPropertyRequired]: require_outbox_listener_delivery_required.eslint$2,
54
+ [require_outbox_listener_delivery_required.RULE_IDS.actorSharedInboxPropertyMismatch]: require_outbox_listener_delivery_required.eslint$3,
55
+ [require_outbox_listener_delivery_required.RULE_IDS.actorPublicKeyRequired]: require_outbox_listener_delivery_required.eslint$4,
56
+ [require_outbox_listener_delivery_required.RULE_IDS.actorAssertionMethodRequired]: require_outbox_listener_delivery_required.eslint$21,
57
+ [require_outbox_listener_delivery_required.RULE_IDS.collectionFilteringNotImplemented]: require_outbox_listener_delivery_required.eslint$1,
58
+ [require_outbox_listener_delivery_required.RULE_IDS.outboxListenerDeliveryRequired]: require_outbox_listener_delivery_required.eslint
59
+ };
60
+ const recommendedRuleIds = [require_outbox_listener_delivery_required.RULE_IDS.actorIdMismatch, require_outbox_listener_delivery_required.RULE_IDS.actorIdRequired];
880
61
  /**
881
62
  * Recommended configuration - enables all rules as warnings
882
63
  */
883
64
  const recommendedRules = (0, _fxts_core.pipe)(rules, _fxts_core.keys, (0, _fxts_core.map)((key) => [`@fedify/lint/${key}`, recommendedRuleIds.includes(key) ? "error" : "warn"]), _fxts_core.fromEntries);
884
- const pluginName = name;
885
- const pluginVersion = version;
65
+ const pluginName = require_outbox_listener_delivery_required.name;
66
+ const pluginVersion = require_outbox_listener_delivery_required.version;
886
67
  /**
887
68
  * Strict configuration - enables all rules as errors
888
69
  */
@@ -912,5 +93,6 @@ const recommendedConfig = {
912
93
  rules: recommendedRules
913
94
  };
914
95
  //#endregion
96
+ exports.__toESM = __toESM;
915
97
  exports.default = recommendedConfig;
916
98
  exports.plugin = plugin;