@fedify/lint 2.4.0-pr.936.41 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1407 +0,0 @@
1
- require("./index.cjs");
2
- let _fxts_core = require("@fxts/core");
3
- //#region deno.json
4
- var name = "@fedify/lint";
5
- var version = "2.4.0-pr.936.41+59c7432c";
6
- //#endregion
7
- //#region src/lib/const.ts
8
- /**
9
- * Mapping of actor property names to their corresponding Context method names
10
- * and dispatcher methods.
11
- * Used by lint rules to validate property existence and correct method usage.
12
- */
13
- const properties = {
14
- id: {
15
- name: "id",
16
- path: ["id"],
17
- getter: "getActorUri",
18
- setter: "setActorDispatcher",
19
- requiresIdentifier: true
20
- },
21
- following: {
22
- name: "following",
23
- path: ["following"],
24
- getter: "getFollowingUri",
25
- setter: "setFollowingDispatcher",
26
- requiresIdentifier: true
27
- },
28
- followers: {
29
- name: "followers",
30
- path: ["followers"],
31
- getter: "getFollowersUri",
32
- setter: "setFollowersDispatcher",
33
- requiresIdentifier: true
34
- },
35
- outbox: {
36
- name: "outbox",
37
- path: ["outbox"],
38
- getter: "getOutboxUri",
39
- setter: "setOutboxDispatcher",
40
- requiresIdentifier: true
41
- },
42
- inbox: {
43
- name: "inbox",
44
- path: ["inbox"],
45
- getter: "getInboxUri",
46
- setter: "setInboxListeners",
47
- requiresIdentifier: true
48
- },
49
- liked: {
50
- name: "liked",
51
- path: ["liked"],
52
- getter: "getLikedUri",
53
- setter: "setLikedDispatcher",
54
- requiresIdentifier: true
55
- },
56
- featured: {
57
- name: "featured",
58
- path: ["featured"],
59
- getter: "getFeaturedUri",
60
- setter: "setFeaturedDispatcher",
61
- requiresIdentifier: true
62
- },
63
- featuredTags: {
64
- name: "featuredTags",
65
- path: ["featuredTags"],
66
- getter: "getFeaturedTagsUri",
67
- setter: "setFeaturedTagsDispatcher",
68
- requiresIdentifier: true
69
- },
70
- sharedInbox: {
71
- name: "sharedInbox",
72
- path: ["endpoints", "sharedInbox"],
73
- getter: "getInboxUri",
74
- setter: "setInboxListeners",
75
- requiresIdentifier: false,
76
- nested: {
77
- parent: "endpoints",
78
- wrapper: "Endpoints"
79
- }
80
- },
81
- uploadMedia: {
82
- name: "uploadMedia",
83
- path: ["endpoints", "uploadMedia"],
84
- getter: "getMediaUploaderUri",
85
- setter: "setMediaUploader",
86
- requiresIdentifier: true,
87
- nested: {
88
- parent: "endpoints",
89
- wrapper: "Endpoints"
90
- }
91
- },
92
- publicKey: {
93
- name: "publicKey",
94
- path: ["publicKey"],
95
- getter: "getActorKeyPairs",
96
- setter: "setKeyPairsDispatcher",
97
- requiresIdentifier: true,
98
- isKeyProperty: true
99
- },
100
- assertionMethod: {
101
- name: "assertionMethod",
102
- path: ["assertionMethod"],
103
- getter: "getActorKeyPairs",
104
- setter: "setKeyPairsDispatcher",
105
- requiresIdentifier: true,
106
- isKeyProperty: true
107
- }
108
- };
109
- /**
110
- * Rule IDs for all Fedify lint rules
111
- */
112
- const RULE_IDS = {
113
- actorIdRequired: "actor-id-required",
114
- actorFollowingPropertyRequired: "actor-following-property-required",
115
- actorFollowersPropertyRequired: "actor-followers-property-required",
116
- actorOutboxPropertyRequired: "actor-outbox-property-required",
117
- actorLikedPropertyRequired: "actor-liked-property-required",
118
- actorFeaturedPropertyRequired: "actor-featured-property-required",
119
- actorFeaturedTagsPropertyRequired: "actor-featured-tags-property-required",
120
- actorInboxPropertyRequired: "actor-inbox-property-required",
121
- actorSharedInboxPropertyRequired: "actor-shared-inbox-property-required",
122
- actorUploadMediaPropertyRequired: "actor-upload-media-property-required",
123
- actorPublicKeyRequired: "actor-public-key-required",
124
- actorAssertionMethodRequired: "actor-assertion-method-required",
125
- actorIdMismatch: "actor-id-mismatch",
126
- actorFollowingPropertyMismatch: "actor-following-property-mismatch",
127
- actorFollowersPropertyMismatch: "actor-followers-property-mismatch",
128
- actorOutboxPropertyMismatch: "actor-outbox-property-mismatch",
129
- actorLikedPropertyMismatch: "actor-liked-property-mismatch",
130
- actorFeaturedPropertyMismatch: "actor-featured-property-mismatch",
131
- actorFeaturedTagsPropertyMismatch: "actor-featured-tags-property-mismatch",
132
- actorInboxPropertyMismatch: "actor-inbox-property-mismatch",
133
- actorSharedInboxPropertyMismatch: "actor-shared-inbox-property-mismatch",
134
- actorUploadMediaPropertyMismatch: "actor-upload-media-property-mismatch",
135
- collectionFilteringNotImplemented: "collection-filtering-not-implemented",
136
- outboxListenerDeliveryRequired: "outbox-listener-delivery-required",
137
- mediaUploaderObjectUriRequired: "media-uploader-object-uri-required",
138
- mediaUploaderAuthorizationRequired: "media-uploader-authorization-required"
139
- };
140
- //#endregion
141
- //#region src/lib/messages.ts
142
- /**
143
- * Generates error message for *-required rules.
144
- * Used when a property is missing from the actor dispatcher return value.
145
- *
146
- * @param name - The property name (e.g., "id", "inbox", "following")
147
- */
148
- const actorPropertyRequired = ({ setter, path, getter, requiresIdentifier = true }) => `When \`${setter}\` is configured, the \`${path.join(".")}\` property is recommended. Use \`${getExpectedCall({
149
- ctxName: "Context",
150
- methodName: getter,
151
- idName: "identifier",
152
- path: path.join("."),
153
- requiresIdentifier
154
- })}\` for the \`${path.join(".")}\` property URI.`;
155
- /**
156
- * Generates error message for *-mismatch rules.
157
- * Used when a property exists but uses the wrong context method.
158
- *
159
- * @param propertyName - The property name or path
160
- * (e.g., "id", "endpoints.sharedInbox")
161
- * @param expectedCall - The expected method call
162
- * (e.g., "ctx.getActorUri(identifier)")
163
- */
164
- const actorPropertyMismatch = (context) => `Actor's \`${context.path}\` property must match \`${getExpectedCall(context)}\`. \
165
- Ensure you're using the correct context method.`;
166
- const getExpectedCall = ({ ctxName, methodName, requiresIdentifier, idName }) => requiresIdentifier ? `${ctxName}.${methodName}(${idName})` : `${ctxName}.${methodName}()`;
167
- /**
168
- * Error message for collection filtering not implemented.
169
- */
170
- 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";
171
- //#endregion
172
- //#region src/lib/utils.ts
173
- const eq = (value) => (other) => value === other;
174
- function cases(pred, ifTrue, ifFalse) {
175
- return (value) => pred(value) ? ifTrue(value) : ifFalse(value);
176
- }
177
- //#endregion
178
- //#region src/lib/pred.ts
179
- function allOf(...predicates) {
180
- return (value) => predicates.every((predicate) => predicate(value));
181
- }
182
- const anyOf = (...predicates) => (value) => predicates.some((predicate) => predicate(value));
183
- const isNode = (obj) => (0, _fxts_core.isObject)(obj) && "type" in obj;
184
- /**
185
- * Checks if a node is of a specific type.
186
- */
187
- const isNodeType = (type) => (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("type"), eq(type));
188
- /**
189
- * Checks if a node is of a specific name.
190
- */
191
- const isNodeName = (name) => (node) => (0, _fxts_core.pipe)(node, (0, _fxts_core.prop)("name"), eq(name));
192
- /**
193
- * Checks if a node's callee is a MemberExpression.
194
- */
195
- const hasMemberExpressionCallee = (node) => node.callee.type === "MemberExpression";
196
- /**
197
- * Checks if a node's callee property is an Identifier.
198
- */
199
- const hasIdentifierProperty = (node) => "callee" in node && "property" in node.callee && "type" in node.callee.property && node.callee.property.type === "Identifier";
200
- /**
201
- * Checks if a node's callee property name matches the given method name.
202
- */
203
- const hasMethodName = (methodName) => (node) => "callee" in node && "property" in node.callee && "name" in node.callee.property && node.callee.property.name === methodName;
204
- /**
205
- * Checks if a CallExpression has minimum required arguments.
206
- */
207
- const hasMinArguments = (min) => (node) => node.arguments.length >= min;
208
- /**
209
- * Checks if an expression is a function (arrow or regular).
210
- */
211
- const isFunction = (expr) => anyOf(isNodeType("ArrowFunctionExpression"), isNodeType("FunctionExpression"))(expr);
212
- /**
213
- * Checks if a CallExpression is a setActorDispatcher call with
214
- * proper structure.
215
- */
216
- const isSetActorDispatcherCall = (node) => allOf(hasMemberExpressionCallee, hasIdentifierProperty, hasMethodName("setActorDispatcher"), hasMinArguments(2))(node);
217
- /**
218
- * Checks if a function has at least n parameters.
219
- */
220
- const hasMinParams = (min) => (fn) => fn.params.length >= min;
221
- //#endregion
222
- //#region src/lib/property-checker.ts
223
- /**
224
- * Checks if a node has a key with a specific name.
225
- */
226
- 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))));
227
- /**
228
- * Checks if a node is a Property with an Identifier key of a specific name.
229
- */
230
- const isPropertyWithName = (propertyName) => (node) => allOf(isNodeType("Property"), hasKeyName(propertyName))(node);
231
- /**
232
- * Creates a predicate function that checks if a nested property exists.
233
- * @param path Array of property names forming the path
234
- * (e.g., ["endpoints", "sharedInbox"])
235
- * @returns A predicate function that checks if the nested property exists
236
- */
237
- function createPropertyChecker(checker) {
238
- const inner = ([first, ...rest]) => (node) => {
239
- if (!isPropertyWithName(first)(node)) return false;
240
- if ((0, _fxts_core.isEmpty)(rest)) return checker(node.value);
241
- if (isNodeType("NewExpression")(node.value)) {
242
- if (node.value.arguments.length === 0) return false;
243
- const firstArg = node.value.arguments[0];
244
- if (!isNodeType("ObjectExpression")(firstArg)) return false;
245
- return firstArg.properties.some(inner(rest));
246
- }
247
- return false;
248
- };
249
- return inner;
250
- }
251
- /**
252
- * Checks if an ObjectExpression node contains a property.
253
- * @param propertyChecker The predicate function to check properties
254
- * @returns A function that checks the ObjectExpression
255
- */
256
- const checkObjectExpression = (propertyChecker) => (obj) => obj.properties.some(propertyChecker);
257
- /**
258
- * Checks if a ConditionalExpression (ternary operator) has the property in
259
- * both branches.
260
- * @param propertyChecker The predicate function to check properties
261
- * @returns A function that checks the ConditionalExpression
262
- */
263
- const checkConditionalExpression = (propertyChecker) => (node) => [node.consequent, node.alternate].every(checkBranchWith(propertyChecker));
264
- 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)))));
265
- /**
266
- * Extracts the first argument if it's an ObjectExpression.
267
- */
268
- 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)));
269
- /**
270
- * Extracts ObjectExpression from NewExpression.
271
- */
272
- const extractObjectExpression = cases(isNodeType("NewExpression"), extractFirstObjectExpression, (0, _fxts_core.always)(null));
273
- /**
274
- * Checks if a ReturnStatement node contains a property.
275
- * @param propertyChecker The predicate function to check properties
276
- * @returns A function that checks the ReturnStatement
277
- */
278
- 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)));
279
- /**
280
- * Creates a function that recursively checks for a property in an AST node.
281
- * @param propertyChecker The predicate function to check properties
282
- * @returns A recursive function that checks the AST node
283
- */
284
- const createPropertySearcher = (propertyChecker) => {
285
- return (node) => {
286
- switch (node.type) {
287
- case "ReturnStatement": return checkReturnStatement(propertyChecker)(node);
288
- case "BlockStatement": return checkAllReturnPaths(propertyChecker)(node);
289
- case "NewExpression": return (0, _fxts_core.pipe)(node, extractFirstObjectExpression, (0, _fxts_core.when)(_fxts_core.isObject, checkObjectExpression(propertyChecker)), Boolean);
290
- default: return false;
291
- }
292
- };
293
- };
294
- 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))));
295
- /**
296
- * Collects all return statements from a node, traversing control flow.
297
- * This handles if/else branches, loops, etc.
298
- */
299
- const collectReturnPaths = (node) => (0, _fxts_core.pipe)(node, flatten, _fxts_core.toArray);
300
- function* flatten(node) {
301
- switch (node.type) {
302
- case "ReturnStatement":
303
- yield node;
304
- return;
305
- case "IfStatement":
306
- if (node.consequent) yield* flatten(node.consequent);
307
- if (node.alternate) yield* flatten(node.alternate);
308
- return;
309
- case "BlockStatement":
310
- for (const statement of node.body) yield* flatten(statement);
311
- return;
312
- case "SwitchStatement":
313
- for (const switchCase of node.cases) for (const statement of switchCase.consequent) yield* flatten(statement);
314
- return;
315
- case "TryStatement":
316
- yield* flatten(node.block);
317
- if (node.handler) yield* flatten(node.handler.body);
318
- if (node.finalizer) yield* flatten(node.finalizer);
319
- return;
320
- case "WhileStatement":
321
- case "DoWhileStatement":
322
- case "ForStatement":
323
- case "ForInStatement":
324
- case "ForOfStatement":
325
- yield* flatten(node.body);
326
- return;
327
- case "LabeledStatement":
328
- yield* flatten(node.body);
329
- return;
330
- case "WithStatement":
331
- yield* flatten(node.body);
332
- return;
333
- default: return;
334
- }
335
- }
336
- //#endregion
337
- //#region src/lib/tracker.ts
338
- /**
339
- * Helper to track variable names that store the result of createFederation() or createFederationBuilder() calls
340
- */
341
- function trackFederationVariables() {
342
- const federationVariables = /* @__PURE__ */ new Set();
343
- const isFederationObject = (obj) => {
344
- switch (obj.type) {
345
- case "Identifier": return federationVariables.has(obj.name);
346
- case "CallExpression":
347
- if (isCreateFederation(obj)) return true;
348
- if (obj.callee.type === "MemberExpression") return isFederationObject(obj.callee.object);
349
- return false;
350
- case "MemberExpression": return isFederationObject(obj.object);
351
- }
352
- return false;
353
- };
354
- return {
355
- VariableDeclarator(node) {
356
- const init = node.init;
357
- const id = node.id;
358
- if (init?.type === "CallExpression") {
359
- if (isCreateFederation(init) && id.type === "Identifier") federationVariables.add(id.name);
360
- }
361
- },
362
- isFederationVariable(name) {
363
- return federationVariables.has(name);
364
- },
365
- isFederationObject
366
- };
367
- }
368
- const isCreateFederation = (node) => node.callee.type === "Identifier" && /^create(Federation|FederationBuilder)$/i.test(node.callee.name);
369
- //#endregion
370
- //#region src/lib/required.ts
371
- /**
372
- * Checks if a CallExpression is a specific dispatcher method call.
373
- */
374
- const isDispatcherMethodCall = (methodName) => (node) => allOf(hasMemberExpressionCallee, hasIdentifierProperty, hasMethodName(methodName))(node);
375
- /**
376
- * Tracks dispatcher method calls on federation objects.
377
- */
378
- const createDispatcherTracker = (dispatcherMethod, federationTracker) => {
379
- let dispatcherConfigured = false;
380
- return {
381
- isDispatcherConfigured: () => dispatcherConfigured,
382
- checkDispatcherCall: (node) => {
383
- if (isDispatcherMethodCall(dispatcherMethod)(node) && federationTracker.isFederationObject(node.callee.object)) dispatcherConfigured = true;
384
- }
385
- };
386
- };
387
- function createRequiredRule(config, description) {
388
- return (context) => {
389
- const federationTracker = trackFederationVariables();
390
- const dispatcherTracker = createDispatcherTracker(config.setter, federationTracker);
391
- const actorDispatchers = [];
392
- const propertySearcher = createPropertySearcher(createPropertyChecker(Boolean)(config.path));
393
- return {
394
- VariableDeclarator: federationTracker.VariableDeclarator,
395
- CallExpression(node) {
396
- dispatcherTracker.checkDispatcherCall(node);
397
- if (!isSetActorDispatcherCall(node)) return;
398
- if (!federationTracker.isFederationObject(node.callee.object)) return;
399
- const dispatcher = node.arguments[1];
400
- if (isFunction(dispatcher)) actorDispatchers.push(dispatcher);
401
- },
402
- "Program:exit"() {
403
- if (!dispatcherTracker.isDispatcherConfigured()) return;
404
- for (const dispatcher of actorDispatchers) if (!propertySearcher(dispatcher.body)) context.report({
405
- node: dispatcher,
406
- ...description
407
- });
408
- }
409
- };
410
- };
411
- }
412
- /**
413
- * Creates a required rule with the given property configuration.
414
- */
415
- function createRequiredRuleDeno(config) {
416
- return { create: createRequiredRule(config, { message: actorPropertyRequired(config) }) };
417
- }
418
- /**
419
- * Creates a required ESLint rule with the given property configuration.
420
- */
421
- function createRequiredRuleEslint(config) {
422
- return {
423
- meta: {
424
- type: "suggestion",
425
- docs: { description: `Ensure actor dispatcher returns ${config.path.join(".")} property.` },
426
- schema: [],
427
- messages: { required: "{{ message }}" }
428
- },
429
- create: createRequiredRule(config, {
430
- messageId: "required",
431
- data: { message: actorPropertyRequired(config) }
432
- })
433
- };
434
- }
435
- createRequiredRuleDeno(properties.assertionMethod);
436
- const eslint$25 = createRequiredRuleEslint(properties.assertionMethod);
437
- //#endregion
438
- //#region src/lib/mismatch.ts
439
- const isIdentifierWithName = (name) => (node) => allOf(isNodeType("Identifier"), isNodeName(name))(node);
440
- /**
441
- * Checks if a node is a CallExpression calling the expected context method.
442
- */
443
- const isExpectedMethodCall = ({ ctxName, idName, methodName, requiresIdentifier }) => (node) => {
444
- if (!isNodeType("CallExpression")(node) || !hasMemberExpressionCallee(node) || !isIdentifierWithName(ctxName)(node.callee.object) || !isIdentifierWithName(methodName)(node.callee.property)) return false;
445
- if (!requiresIdentifier) return (0, _fxts_core.isEmpty)(node.arguments);
446
- return allOf((0, _fxts_core.negate)(_fxts_core.isEmpty), (0, _fxts_core.some)(isIdentifierWithName(idName)))(node.arguments);
447
- };
448
- /**
449
- * Extracts parameter names from a function.
450
- */
451
- const extractParams = (fn) => {
452
- const params = fn.params;
453
- if (params.length < 2) return [null, null];
454
- return params.slice(0, 2).map(getNameIfIdentifier);
455
- };
456
- const getNameIfIdentifier = (node) => node?.type === "Identifier" ? node.name : null;
457
- function createMismatchRule(config, describe) {
458
- return (context) => {
459
- const tracker = trackFederationVariables();
460
- return {
461
- VariableDeclarator: tracker.VariableDeclarator,
462
- CallExpression(node) {
463
- if (!isSetActorDispatcherCall(node) || !hasMemberExpressionCallee(node) || !tracker.isFederationObject(node.callee.object)) return;
464
- const dispatcherArg = node.arguments[1];
465
- if (!isFunction(dispatcherArg)) return;
466
- const [ctxName, idName] = extractParams(dispatcherArg);
467
- if (!ctxName || !idName) return;
468
- const methodCallContext = {
469
- path: config.path.join("."),
470
- ctxName,
471
- idName,
472
- methodName: config.getter,
473
- requiresIdentifier: config.requiresIdentifier
474
- };
475
- if (!createPropertySearcher(createPropertyChecker(Boolean)(config.path))(dispatcherArg.body)) return;
476
- if (!createPropertySearcher(createPropertyChecker(isExpectedMethodCall(methodCallContext))(config.path))(dispatcherArg.body)) context.report({
477
- node: dispatcherArg,
478
- ...describe(methodCallContext)
479
- });
480
- }
481
- };
482
- };
483
- }
484
- /**
485
- * Creates a lint rule that checks if a property uses
486
- * the correct context method.
487
- *
488
- * @param config Property configuration containing name, getter, setter, and
489
- * nested info
490
- * @returns A Deno lint rule
491
- */
492
- const createMismatchRuleDeno = (config) => ({ create: createMismatchRule(config, (context) => ({ message: actorPropertyMismatch(context) })) });
493
- const createMismatchRuleEslint = (config) => ({
494
- meta: {
495
- type: "problem",
496
- docs: { description: `Ensure actor's ${config.path.join(".")} property uses correct context method` },
497
- schema: [],
498
- messages: { mismatch: "{{ message }}" }
499
- },
500
- create: createMismatchRule(config, (context) => ({
501
- messageId: "mismatch",
502
- data: { message: actorPropertyMismatch(context) }
503
- }))
504
- });
505
- createMismatchRuleDeno(properties.featured);
506
- const eslint$24 = createMismatchRuleEslint(properties.featured);
507
- createRequiredRuleDeno(properties.featured);
508
- const eslint$23 = createRequiredRuleEslint(properties.featured);
509
- createMismatchRuleDeno(properties.featuredTags);
510
- const eslint$22 = createMismatchRuleEslint(properties.featuredTags);
511
- createRequiredRuleDeno(properties.featuredTags);
512
- const eslint$21 = createRequiredRuleEslint(properties.featuredTags);
513
- createMismatchRuleDeno(properties.followers);
514
- const eslint$20 = createMismatchRuleEslint(properties.followers);
515
- createRequiredRuleDeno(properties.followers);
516
- const eslint$19 = createRequiredRuleEslint(properties.followers);
517
- createMismatchRuleDeno(properties.following);
518
- const eslint$18 = createMismatchRuleEslint(properties.following);
519
- createRequiredRuleDeno(properties.following);
520
- const eslint$17 = createRequiredRuleEslint(properties.following);
521
- createMismatchRuleDeno(properties.id);
522
- const eslint$16 = createMismatchRuleEslint(properties.id);
523
- createRequiredRuleDeno(properties.id);
524
- const eslint$15 = createRequiredRuleEslint(properties.id);
525
- createMismatchRuleDeno(properties.inbox);
526
- const eslint$14 = createMismatchRuleEslint(properties.inbox);
527
- createRequiredRuleDeno(properties.inbox);
528
- const eslint$13 = createRequiredRuleEslint(properties.inbox);
529
- createMismatchRuleDeno(properties.liked);
530
- const eslint$12 = createMismatchRuleEslint(properties.liked);
531
- createRequiredRuleDeno(properties.liked);
532
- const eslint$11 = createRequiredRuleEslint(properties.liked);
533
- createMismatchRuleDeno(properties.outbox);
534
- const eslint$10 = createMismatchRuleEslint(properties.outbox);
535
- createRequiredRuleDeno(properties.outbox);
536
- const eslint$9 = createRequiredRuleEslint(properties.outbox);
537
- createRequiredRuleDeno(properties.publicKey);
538
- const eslint$8 = createRequiredRuleEslint(properties.publicKey);
539
- createMismatchRuleDeno(properties.sharedInbox);
540
- const eslint$7 = createMismatchRuleEslint(properties.sharedInbox);
541
- createRequiredRuleDeno(properties.sharedInbox);
542
- const eslint$6 = createRequiredRuleEslint(properties.sharedInbox);
543
- createMismatchRuleDeno(properties.uploadMedia);
544
- const eslint$5 = createMismatchRuleEslint(properties.uploadMedia);
545
- createRequiredRuleDeno(properties.uploadMedia);
546
- const eslint$4 = createRequiredRuleEslint(properties.uploadMedia);
547
- //#endregion
548
- //#region src/rules/collection-filtering-not-implemented.ts
549
- /**
550
- * The followers dispatcher method that supports filtering.
551
- * Only setFollowersDispatcher uses the filter parameter for
552
- * followers collection synchronization.
553
- * See: https://fedify.dev/manual/collections#filtering-by-server
554
- */
555
- const FILTER_NEEDED = ["setFollowersDispatcher"];
556
- /**
557
- * Checks if a node is a setFollowersDispatcher call.
558
- */
559
- const isFollowersDispatcherCall = (node) => "callee" in node && node.callee && node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && FILTER_NEEDED.includes(node.callee.property.name);
560
- /**
561
- * Checks if a function node has the filter parameter (4th parameter).
562
- * CollectionDispatcher signature: (context, identifier, cursor, filter?) => ...
563
- */
564
- const hasFilterParameter = hasMinParams(4);
565
- const eslint$3 = {
566
- meta: {
567
- type: "suggestion",
568
- docs: { description: "Ensure followers dispatcher implements filtering" },
569
- schema: [],
570
- messages: { filterRequired: COLLECTION_FILTERING_NOT_IMPLEMENTED_ERROR }
571
- },
572
- create(context) {
573
- const federationTracker = trackFederationVariables();
574
- return {
575
- VariableDeclarator: federationTracker.VariableDeclarator,
576
- CallExpression(node) {
577
- if (!isFollowersDispatcherCall(node)) return;
578
- if (!federationTracker.isFederationObject(node.callee.object)) return;
579
- const dispatcherArg = node.arguments[1];
580
- if (!isFunction(dispatcherArg)) return;
581
- if (!hasFilterParameter(dispatcherArg)) context.report({
582
- node: dispatcherArg,
583
- messageId: "filterRequired"
584
- });
585
- }
586
- };
587
- }
588
- };
589
- //#endregion
590
- //#region src/rules/media-uploader-authorization-required.ts
591
- const MESSAGE$2 = "setMediaUploader() should be protected with .authorize(); without it the endpoint accepts uploads from anyone who can reach the URL. Add .authorize() unless a public upload endpoint is intended.";
592
- function getParent(node) {
593
- if (node == null || typeof node !== "object") return null;
594
- const parent = node.parent;
595
- return isNode(parent) ? parent : null;
596
- }
597
- /**
598
- * Whether the given `setMediaUploader(...)` call is directly chained with
599
- * `.authorize(...)`, i.e. `federation.setMediaUploader(...).authorize(...)`.
600
- */
601
- function isDirectlyAuthorized(callNode) {
602
- const member = getParent(callNode);
603
- if (member == null || member.type !== "MemberExpression" || member.computed === true) return false;
604
- const property = member.property;
605
- if (!isNode(property) || property.type !== "Identifier" || property.name !== "authorize") return false;
606
- const call = getParent(member);
607
- return call != null && call.type === "CallExpression" && call.callee === member;
608
- }
609
- function createRule$2(buildReport) {
610
- return (context) => {
611
- const federationTracker = trackFederationVariables();
612
- const unchained = [];
613
- const authorizedVars = /* @__PURE__ */ new Set();
614
- return {
615
- VariableDeclarator: federationTracker.VariableDeclarator,
616
- CallExpression(node) {
617
- if (hasMemberExpressionCallee(node) && hasIdentifierProperty(node) && hasMethodName("authorize")(node)) {
618
- const object = node.callee.object;
619
- if (isNode(object) && object.type === "Identifier") authorizedVars.add(object.name);
620
- return;
621
- }
622
- if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("setMediaUploader")(node) || !federationTracker.isFederationObject(node.callee.object)) return;
623
- if (isDirectlyAuthorized(node)) return;
624
- const parent = getParent(node);
625
- let varName = null;
626
- if (parent != null && parent.type === "VariableDeclarator") {
627
- const id = parent.id;
628
- if (isNode(id) && id.type === "Identifier") varName = id.name;
629
- }
630
- unchained.push({
631
- node,
632
- varName
633
- });
634
- },
635
- "Program:exit"() {
636
- for (const { node, varName } of unchained) {
637
- if (varName != null && authorizedVars.has(varName)) continue;
638
- context.report({
639
- node,
640
- ...buildReport
641
- });
642
- }
643
- }
644
- };
645
- };
646
- }
647
- const eslint$2 = {
648
- meta: {
649
- type: "suggestion",
650
- docs: { description: "Warn when setMediaUploader() is registered without an authorize() hook" },
651
- schema: [],
652
- messages: { required: "{{ message }}" }
653
- },
654
- create: createRule$2({
655
- messageId: "required",
656
- data: { message: MESSAGE$2 }
657
- })
658
- };
659
- //#endregion
660
- //#region src/rules/media-uploader-object-uri-required.ts
661
- const MESSAGE$1 = "setMediaUploader() callbacks should return a value derived from ctx.getObjectUri(): either return ctx.getObjectUri(...) directly, or an object whose id is ctx.getObjectUri(...).";
662
- const GETTER_NAME = "getObjectUri";
663
- const getMemberPropertyName$1 = (node) => {
664
- if (node.type !== "MemberExpression") return null;
665
- const property = node.property;
666
- if (property.type === "Identifier") return property.name;
667
- if (property.type === "Literal" && typeof property.value === "string") return property.value;
668
- return null;
669
- };
670
- function unwrapAssignmentPattern(node) {
671
- let current = node ?? null;
672
- while (current?.type === "AssignmentPattern") current = current.left;
673
- return current;
674
- }
675
- /**
676
- * Resolves the callback argument to a function node, following simple bindings
677
- * when the callback is passed by reference (identifier or object member).
678
- */
679
- const resolveCallbackReference = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
680
- if (isFunction(expr)) return expr;
681
- if (expr.type === "Identifier") {
682
- if (seen.has(expr.name)) return null;
683
- seen.add(expr.name);
684
- const binding = bindings.get(expr.name);
685
- if (binding == null || !isNode(binding)) return null;
686
- if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
687
- if (binding.type === "Identifier") return resolveCallbackReference(binding, bindings, seen);
688
- return null;
689
- }
690
- if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
691
- const binding = bindings.get(expr.object.name);
692
- if (binding == null || !isNode(binding) || binding.type !== "ObjectExpression") return null;
693
- const propertyName = getMemberPropertyName$1(expr);
694
- if (propertyName == null) return null;
695
- for (const prop of binding.properties) {
696
- if (!isNode(prop) || prop.type !== "Property") continue;
697
- 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;
698
- const value = prop.value;
699
- if (isFunction(value) || value.type === "FunctionDeclaration") return value;
700
- }
701
- }
702
- return null;
703
- };
704
- /**
705
- * Returns the identifier name of the callback's context parameter, used to
706
- * require that `getObjectUri()` is called as a member of the context itself
707
- * (`ctx.getObjectUri(...)`), not on some other object. Returns `null` when the
708
- * parameter is destructured: destructuring `getObjectUri` off the context
709
- * (`({ getObjectUri }) => getObjectUri(...)`) loses the method's receiver and
710
- * throws at runtime, so that form is intentionally not recognized as valid.
711
- */
712
- function getContextParamName(callback) {
713
- const contextParam = unwrapAssignmentPattern(callback.params[0]);
714
- return contextParam?.type === "Identifier" ? contextParam.name : null;
715
- }
716
- const FUNCTION_NODE_TYPES = new Set([
717
- "FunctionDeclaration",
718
- "FunctionExpression",
719
- "ArrowFunctionExpression"
720
- ]);
721
- /**
722
- * Collects `const`/`let`/`var name = init` bindings declared inside the
723
- * callback body (excluding nested functions), so an identifier used in a
724
- * returned expression can be resolved back to the expression it was assigned.
725
- */
726
- function collectLocalBindings(node, bindings) {
727
- if (node == null || typeof node !== "object") return;
728
- if (Array.isArray(node)) {
729
- for (const child of node) collectLocalBindings(child, bindings);
730
- return;
731
- }
732
- const n = node;
733
- if (typeof n.type !== "string") return;
734
- if (FUNCTION_NODE_TYPES.has(n.type)) return;
735
- if (n.type === "VariableDeclarator") {
736
- const id = n.id;
737
- if (id != null && isNode(id) && id.type === "Identifier" && n.init != null) {
738
- if (!bindings.has(id.name)) bindings.set(id.name, n.init);
739
- }
740
- }
741
- for (const key in n) {
742
- if (key === "parent") continue;
743
- collectLocalBindings(n[key], bindings);
744
- }
745
- }
746
- /**
747
- * Collects the returned expressions of the callback: the argument of every
748
- * `return` statement (excluding nested functions), or, for an
749
- * expression-bodied arrow, the body expression itself. A bare `return;`
750
- * contributes `null`.
751
- */
752
- function collectReturnedExpressions(callback) {
753
- const body = callback.body;
754
- if (body != null && isNode(body) && body.type !== "BlockStatement") return [body];
755
- const result = [];
756
- const walk = (node) => {
757
- if (node == null || typeof node !== "object") return;
758
- if (Array.isArray(node)) {
759
- for (const child of node) walk(child);
760
- return;
761
- }
762
- const n = node;
763
- if (typeof n.type !== "string") return;
764
- if (n.type === "ReturnStatement") {
765
- result.push(n.argument ?? null);
766
- return;
767
- }
768
- if (FUNCTION_NODE_TYPES.has(n.type)) return;
769
- for (const key in n) {
770
- if (key === "parent") continue;
771
- walk(n[key]);
772
- }
773
- };
774
- walk(body);
775
- return result;
776
- }
777
- function toNode(value) {
778
- if (value == null || typeof value !== "object" || Array.isArray(value)) return null;
779
- const n = value;
780
- return typeof n.type === "string" ? n : null;
781
- }
782
- /** Whether the node is a `Promise.resolve(x)` call. */
783
- function isPromiseResolveCall(expr) {
784
- if (expr.type !== "CallExpression") return false;
785
- const callee = toNode(expr.callee);
786
- if (callee == null || callee.type !== "MemberExpression" || getMemberPropertyName$1(callee) !== "resolve") return false;
787
- const object = toNode(callee.object);
788
- return object != null && object.type === "Identifier" && object.name === "Promise";
789
- }
790
- /**
791
- * Strips wrapper expressions that do not change the underlying value: `await`,
792
- * parentheses, `as`/`<T>`/`!`/`satisfies` type assertions, and
793
- * `Promise.resolve(...)` (a synchronous callback may wrap its result in an
794
- * already-resolved promise, which `MediaUploaderCallback` permits).
795
- */
796
- function unwrapExpression(value) {
797
- let current = toNode(value);
798
- while (current != null) if (current.type === "AwaitExpression") current = toNode(current.argument);
799
- else if (current.type === "TSAsExpression" || current.type === "TSNonNullExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "ParenthesizedExpression") current = toNode(current.expression);
800
- else if (isPromiseResolveCall(current)) {
801
- const args = current.arguments;
802
- current = Array.isArray(args) && args.length === 1 ? toNode(args[0]) : null;
803
- } else return current;
804
- return null;
805
- }
806
- /**
807
- * Whether the expression is a member call `<ctx>.getObjectUri(...)` whose
808
- * receiver is the callback's context parameter. A call on any other object
809
- * (e.g. `helper.getObjectUri(...)`), or a bare `getObjectUri(...)` from a
810
- * destructured parameter (which loses the receiver and throws at runtime),
811
- * does not count.
812
- */
813
- function isGetObjectUriCall(expr, ctx) {
814
- if (expr.type !== "CallExpression") return false;
815
- if (ctx.ctxName == null) return false;
816
- const callee = toNode(expr.callee);
817
- if (callee == null || callee.type !== "MemberExpression" || getMemberPropertyName$1(callee) !== GETTER_NAME) return false;
818
- const object = unwrapExpression(callee.object);
819
- return object != null && object.type === "Identifier" && object.name === ctx.ctxName;
820
- }
821
- function isUrlConstructor(expr) {
822
- const callee = toNode(expr.callee);
823
- return callee != null && callee.type === "Identifier" && callee.name === "URL";
824
- }
825
- /** Finds the first `ObjectExpression` argument of a `NewExpression`. */
826
- function extractObjectArg(newExpr) {
827
- const args = newExpr.arguments;
828
- if (!Array.isArray(args)) return null;
829
- for (const arg of args) {
830
- const a = toNode(arg);
831
- if (a != null && a.type === "ObjectExpression") return a;
832
- }
833
- return null;
834
- }
835
- /**
836
- * Whether a URL-valued expression is derived from `getObjectUri()`: a direct
837
- * call, an identifier whose local binding resolves to one
838
- * (`const uri = ctx.getObjectUri(...); return uri`), both branches of a
839
- * ternary, or `new URL(<derived>)`.
840
- */
841
- function urlDerivesFromGetObjectUri(value, ctx, seen) {
842
- const expr = unwrapExpression(value);
843
- if (expr == null) return false;
844
- if (isGetObjectUriCall(expr, ctx)) return true;
845
- if (expr.type === "Identifier") {
846
- const name = expr.name;
847
- if (typeof name === "string" && ctx.bindings.has(name) && !seen.has(name)) return urlDerivesFromGetObjectUri(ctx.bindings.get(name), ctx, new Set(seen).add(name));
848
- return false;
849
- }
850
- if (expr.type === "ConditionalExpression") return urlDerivesFromGetObjectUri(expr.consequent, ctx, seen) && urlDerivesFromGetObjectUri(expr.alternate, ctx, seen);
851
- if (expr.type === "NewExpression" && isUrlConstructor(expr)) {
852
- const args = expr.arguments;
853
- return Array.isArray(args) && args.length > 0 && urlDerivesFromGetObjectUri(args[0], ctx, seen);
854
- }
855
- return false;
856
- }
857
- const SPREAD_TYPES = new Set([
858
- "SpreadElement",
859
- "SpreadProperty",
860
- "ExperimentalSpreadProperty"
861
- ]);
862
- /**
863
- * Whether an object literal's *effective* own `id` is derived from
864
- * `getObjectUri`. Properties are evaluated in order so a later duplicate `id`
865
- * wins, and a spread or computed key appearing after the last static `id`
866
- * (which could override it) is treated as unsafe. An object with no static
867
- * `id` property (its `id` might come from an unresolved spread) is also
868
- * unsafe.
869
- */
870
- function objectIdDerivesFromGetObjectUri(objExpr, ctx, seen) {
871
- const props = objExpr.properties;
872
- if (!Array.isArray(props)) return false;
873
- let idValue;
874
- let idIsSet = false;
875
- let overridableAfterId = false;
876
- for (const prop of props) {
877
- const p = toNode(prop);
878
- if (p == null) return false;
879
- if (SPREAD_TYPES.has(p.type) || p.type === "Property" && p.computed === true) {
880
- if (idIsSet) overridableAfterId = true;
881
- continue;
882
- }
883
- if (p.type !== "Property") continue;
884
- const key = toNode(p.key);
885
- if ((key == null ? null : key.type === "Identifier" && typeof key.name === "string" ? key.name : key.type === "Literal" && typeof key.value === "string" ? key.value : null) !== "id") continue;
886
- idValue = p.value;
887
- idIsSet = true;
888
- overridableAfterId = false;
889
- }
890
- if (!idIsSet || overridableAfterId) return false;
891
- return urlDerivesFromGetObjectUri(idValue, ctx, seen);
892
- }
893
- /**
894
- * Whether a single returned expression is valid: a URL derived from
895
- * `getObjectUri()`, or a vocab object (a `new X({...})` expression) whose own
896
- * `id` is derived from it. The object is judged solely by its `id`, so
897
- * `getObjectUri()` appearing in another property (e.g. `url`) does not excuse a
898
- * hard-coded `id`. A plain object literal is rejected: the handler calls
899
- * `toJsonLd()` on the result, so a bare `{ ... }` (which lacks it) is not a
900
- * valid return.
901
- */
902
- function returnDerivesFromGetObjectUri(value, ctx, seen) {
903
- const expr = unwrapExpression(value);
904
- if (expr == null) return false;
905
- if (expr.type === "Identifier") {
906
- const name = expr.name;
907
- if (typeof name === "string" && ctx.bindings.has(name) && !seen.has(name)) return returnDerivesFromGetObjectUri(ctx.bindings.get(name), ctx, new Set(seen).add(name));
908
- return false;
909
- }
910
- if (expr.type === "ConditionalExpression") return returnDerivesFromGetObjectUri(expr.consequent, ctx, seen) && returnDerivesFromGetObjectUri(expr.alternate, ctx, seen);
911
- if (expr.type === "NewExpression") {
912
- if (isUrlConstructor(expr)) return urlDerivesFromGetObjectUri(expr, ctx, seen);
913
- const objArg = extractObjectArg(expr);
914
- if (objArg == null) return false;
915
- return objectIdDerivesFromGetObjectUri(objArg, ctx, seen);
916
- }
917
- return urlDerivesFromGetObjectUri(expr, ctx, seen);
918
- }
919
- /**
920
- * Determines whether every returned value of the callback is derived from
921
- * `getObjectUri`. Returns `false` when the callback has no return value or any
922
- * returned value is not derived from `getObjectUri`.
923
- */
924
- function callbackDerivesFromGetObjectUri(callback) {
925
- const returnedExpressions = collectReturnedExpressions(callback);
926
- if (returnedExpressions.length < 1) return false;
927
- const bindings = /* @__PURE__ */ new Map();
928
- collectLocalBindings(callback.body, bindings);
929
- const ctx = {
930
- ctxName: getContextParamName(callback),
931
- bindings
932
- };
933
- return returnedExpressions.every((expr) => returnDerivesFromGetObjectUri(expr, ctx, /* @__PURE__ */ new Set()));
934
- }
935
- function createRule$1(buildReport) {
936
- return (context) => {
937
- const federationTracker = trackFederationVariables();
938
- const bindings = /* @__PURE__ */ new Map();
939
- const pendingCalls = [];
940
- const inspectCall = (node) => {
941
- if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("setMediaUploader")(node) || node.arguments.length < 2) return;
942
- if (!federationTracker.isFederationObject(node.callee.object)) return;
943
- const callbackArg = node.arguments[1];
944
- const callback = isNode(callbackArg) && isFunction(callbackArg) ? callbackArg : isNode(callbackArg) ? resolveCallbackReference(callbackArg, bindings) : null;
945
- if (callback == null) return;
946
- if (callbackDerivesFromGetObjectUri(callback)) return;
947
- context.report({
948
- node: callback,
949
- ...buildReport
950
- });
951
- };
952
- return {
953
- VariableDeclarator(node) {
954
- federationTracker.VariableDeclarator(node);
955
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
956
- },
957
- FunctionDeclaration(node) {
958
- if (node.id != null) bindings.set(node.id.name, node);
959
- },
960
- CallExpression(node) {
961
- pendingCalls.push(node);
962
- },
963
- "Program:exit"() {
964
- for (const node of pendingCalls) inspectCall(node);
965
- }
966
- };
967
- };
968
- }
969
- const eslint$1 = {
970
- meta: {
971
- type: "suggestion",
972
- docs: { description: "Warn when a setMediaUploader() callback does not derive its return value from ctx.getObjectUri()" },
973
- schema: [],
974
- messages: { required: "{{ message }}" }
975
- },
976
- create: createRule$1({
977
- messageId: "required",
978
- data: { message: MESSAGE$1 }
979
- })
980
- };
981
- //#endregion
982
- //#region src/rules/outbox-listener-delivery-required.ts
983
- const MESSAGE = "Outbox listeners should deliver posted activities explicitly with ctx.sendActivity() or ctx.forwardActivity().";
984
- const isChainedFromOutboxListeners = (expr, federationTracker) => {
985
- if (expr.type !== "CallExpression") return false;
986
- if (!hasMemberExpressionCallee(expr) || !hasIdentifierProperty(expr)) return false;
987
- const methodName = expr.callee.property.name;
988
- if (methodName === "setOutboxListeners") return federationTracker.isFederationObject(expr.callee.object);
989
- if (methodName === "authorize" || methodName === "onError" || methodName === "on") return isChainedFromOutboxListeners(expr.callee.object, federationTracker);
990
- return false;
991
- };
992
- const DELIVERY_METHOD_NAMES = new Set(["sendActivity", "forwardActivity"]);
993
- const getMemberPropertyName = (expr) => {
994
- if (expr.type !== "MemberExpression") return null;
995
- const property = expr.property;
996
- if (property.type === "Identifier") return property.name;
997
- if (property.type === "Literal" && typeof property.value === "string") return property.value;
998
- return null;
999
- };
1000
- function unwrapContextParam(node) {
1001
- let current = node ?? null;
1002
- while (current?.type === "AssignmentPattern") current = current.left;
1003
- return current;
1004
- }
1005
- function escapeRegExp(value) {
1006
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
1007
- }
1008
- function stripCommentsAndStrings(code) {
1009
- let result = "";
1010
- let index = 0;
1011
- const skipQuotedString = (quote) => {
1012
- const start = index;
1013
- index += 1;
1014
- while (index < code.length) {
1015
- const char = code[index];
1016
- if (char === "\\") {
1017
- index += 2;
1018
- continue;
1019
- }
1020
- index += 1;
1021
- if (char === quote) break;
1022
- }
1023
- const literal = code.slice(start, index);
1024
- const value = literal.slice(1, -1);
1025
- result += DELIVERY_METHOD_NAMES.has(value) ? literal : `${quote}${quote}`;
1026
- };
1027
- const stripTemplateLiteral = () => {
1028
- const start = index;
1029
- index += 1;
1030
- let raw = "";
1031
- let hasExpression = false;
1032
- while (index < code.length) {
1033
- const char = code[index];
1034
- if (char === "\\") {
1035
- raw += char;
1036
- raw += code[index + 1] ?? "";
1037
- index += 2;
1038
- continue;
1039
- }
1040
- if (char === "`") {
1041
- index += 1;
1042
- if (!hasExpression && DELIVERY_METHOD_NAMES.has(raw)) result += code.slice(start, index);
1043
- else result += "``";
1044
- return;
1045
- }
1046
- if (char === "$" && code[index + 1] === "{") {
1047
- hasExpression = true;
1048
- result += "`${";
1049
- index += 2;
1050
- let depth = 1;
1051
- while (index < code.length && depth > 0) {
1052
- const exprChar = code[index];
1053
- const next = code[index + 1];
1054
- if (exprChar === "'" || exprChar === "\"") {
1055
- skipQuotedString(exprChar);
1056
- continue;
1057
- }
1058
- if (exprChar === "`") {
1059
- stripTemplateLiteral();
1060
- continue;
1061
- }
1062
- if (exprChar === "/" && next === "*") {
1063
- index += 2;
1064
- while (index < code.length) {
1065
- if (code[index] === "*" && code[index + 1] === "/") {
1066
- index += 2;
1067
- break;
1068
- }
1069
- index += 1;
1070
- }
1071
- continue;
1072
- }
1073
- if (exprChar === "/" && next === "/") {
1074
- index += 2;
1075
- while (index < code.length && code[index] !== "\n") index += 1;
1076
- continue;
1077
- }
1078
- result += exprChar;
1079
- index += 1;
1080
- if (exprChar === "{") depth += 1;
1081
- else if (exprChar === "}") depth -= 1;
1082
- }
1083
- continue;
1084
- }
1085
- raw += char;
1086
- index += 1;
1087
- }
1088
- result += "``";
1089
- };
1090
- while (index < code.length) {
1091
- const char = code[index];
1092
- const next = code[index + 1];
1093
- if (char === "/" && next === "*") {
1094
- index += 2;
1095
- while (index < code.length) {
1096
- if (code[index] === "*" && code[index + 1] === "/") {
1097
- index += 2;
1098
- break;
1099
- }
1100
- index += 1;
1101
- }
1102
- continue;
1103
- }
1104
- if (char === "/" && next === "/") {
1105
- index += 2;
1106
- while (index < code.length && code[index] !== "\n") index += 1;
1107
- continue;
1108
- }
1109
- if (char === "'" || char === "\"") {
1110
- skipQuotedString(char);
1111
- continue;
1112
- }
1113
- if (char === "`") {
1114
- stripTemplateLiteral();
1115
- continue;
1116
- }
1117
- result += char;
1118
- index += 1;
1119
- }
1120
- return result;
1121
- }
1122
- function getDeliveryAliasName(node) {
1123
- if (node.type === "Identifier") return node.name;
1124
- if (node.type === "AssignmentPattern" && node.left.type === "Identifier") return node.left.name;
1125
- return null;
1126
- }
1127
- function buildContextExpressionPattern(contextName) {
1128
- const name = escapeRegExp(contextName);
1129
- const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
1130
- return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
1131
- }
1132
- const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
1133
- if (isFunction(expr)) return expr;
1134
- if (expr.type === "Identifier") {
1135
- if (seen.has(expr.name)) return null;
1136
- seen.add(expr.name);
1137
- const binding = bindings.get(expr.name);
1138
- if (binding == null || !isNode(binding)) return null;
1139
- if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
1140
- if (binding.type === "Identifier") return resolveListenerReference(binding, bindings, seen);
1141
- return null;
1142
- }
1143
- if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
1144
- const binding = bindings.get(expr.object.name);
1145
- if (binding == null || !isNode(binding) || binding.type !== "ObjectExpression") return null;
1146
- const propertyName = getMemberPropertyName(expr);
1147
- if (propertyName == null) return null;
1148
- for (const prop of binding.properties) {
1149
- if (!isNode(prop) || prop.type !== "Property") continue;
1150
- 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;
1151
- const value = prop.value;
1152
- if (isFunction(value) || value.type === "FunctionDeclaration") return value;
1153
- }
1154
- }
1155
- return null;
1156
- };
1157
- const listenerCallsDeliveryMethod = (sourceCode, listener) => {
1158
- const code = stripCommentsAndStrings(sourceCode.getText(listener));
1159
- const aliases = /* @__PURE__ */ new Set();
1160
- const contextParam = unwrapContextParam(listener.params[0]);
1161
- const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
1162
- if (contextParam?.type === "ObjectPattern") for (const prop of contextParam.properties) {
1163
- if (!isNode(prop) || prop.type !== "Property") continue;
1164
- const keyName = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "Literal" && typeof prop.key.value === "string" ? prop.key.value : null;
1165
- if (keyName == null || !DELIVERY_METHOD_NAMES.has(keyName)) continue;
1166
- const alias = getDeliveryAliasName(prop.value);
1167
- if (alias != null) aliases.add(alias);
1168
- }
1169
- if (contextName != null) {
1170
- const contextExpr = buildContextExpressionPattern(contextName);
1171
- 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;
1172
- const destructuringPattern = new RegExp(String.raw`(?:const|let|var)\s*{([^}]*)}\s*=\s*${contextExpr}`, "g");
1173
- for (const match of code.matchAll(destructuringPattern)) {
1174
- const fields = match[1].split(",").map((field) => field.trim()).filter(Boolean);
1175
- for (const field of fields) {
1176
- const [sourceName, aliasName] = field.split(":").map((part) => part.trim());
1177
- if (!DELIVERY_METHOD_NAMES.has(sourceName)) continue;
1178
- aliases.add(aliasName ?? sourceName);
1179
- }
1180
- }
1181
- 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");
1182
- for (const match of code.matchAll(aliasPattern)) aliases.add(match[1]);
1183
- }
1184
- return globalThis.Array.from(aliases).some((alias) => new RegExp(String.raw`\b${escapeRegExp(alias)}\s*\(`).test(code));
1185
- };
1186
- function createRule(buildReport) {
1187
- return (context) => {
1188
- const federationTracker = trackFederationVariables();
1189
- const bindings = /* @__PURE__ */ new Map();
1190
- const pendingCalls = [];
1191
- const sourceCode = context.sourceCode;
1192
- const inspectCall = (node) => {
1193
- if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
1194
- if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
1195
- const listener = node.arguments[1];
1196
- const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveListenerReference(listener, bindings) : null;
1197
- if (resolvedListener == null) return;
1198
- if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
1199
- context.report({
1200
- node: resolvedListener,
1201
- ...buildReport
1202
- });
1203
- };
1204
- return {
1205
- VariableDeclarator(node) {
1206
- federationTracker.VariableDeclarator(node);
1207
- if (node.id.type === "Identifier" && node.init != null) bindings.set(node.id.name, node.init);
1208
- },
1209
- FunctionDeclaration(node) {
1210
- if (node.id != null) bindings.set(node.id.name, node);
1211
- },
1212
- CallExpression(node) {
1213
- pendingCalls.push(node);
1214
- },
1215
- "Program:exit"() {
1216
- for (const node of pendingCalls) inspectCall(node);
1217
- }
1218
- };
1219
- };
1220
- }
1221
- const eslint = {
1222
- meta: {
1223
- type: "suggestion",
1224
- docs: { description: "Warn when an outbox listener omits explicit delivery methods" },
1225
- schema: [],
1226
- messages: { required: "{{ message }}" }
1227
- },
1228
- create: createRule({
1229
- messageId: "required",
1230
- data: { message: MESSAGE }
1231
- })
1232
- };
1233
- //#endregion
1234
- Object.defineProperty(exports, "RULE_IDS", {
1235
- enumerable: true,
1236
- get: function() {
1237
- return RULE_IDS;
1238
- }
1239
- });
1240
- Object.defineProperty(exports, "eslint", {
1241
- enumerable: true,
1242
- get: function() {
1243
- return eslint;
1244
- }
1245
- });
1246
- Object.defineProperty(exports, "eslint$1", {
1247
- enumerable: true,
1248
- get: function() {
1249
- return eslint$1;
1250
- }
1251
- });
1252
- Object.defineProperty(exports, "eslint$10", {
1253
- enumerable: true,
1254
- get: function() {
1255
- return eslint$10;
1256
- }
1257
- });
1258
- Object.defineProperty(exports, "eslint$11", {
1259
- enumerable: true,
1260
- get: function() {
1261
- return eslint$11;
1262
- }
1263
- });
1264
- Object.defineProperty(exports, "eslint$12", {
1265
- enumerable: true,
1266
- get: function() {
1267
- return eslint$12;
1268
- }
1269
- });
1270
- Object.defineProperty(exports, "eslint$13", {
1271
- enumerable: true,
1272
- get: function() {
1273
- return eslint$13;
1274
- }
1275
- });
1276
- Object.defineProperty(exports, "eslint$14", {
1277
- enumerable: true,
1278
- get: function() {
1279
- return eslint$14;
1280
- }
1281
- });
1282
- Object.defineProperty(exports, "eslint$15", {
1283
- enumerable: true,
1284
- get: function() {
1285
- return eslint$15;
1286
- }
1287
- });
1288
- Object.defineProperty(exports, "eslint$16", {
1289
- enumerable: true,
1290
- get: function() {
1291
- return eslint$16;
1292
- }
1293
- });
1294
- Object.defineProperty(exports, "eslint$17", {
1295
- enumerable: true,
1296
- get: function() {
1297
- return eslint$17;
1298
- }
1299
- });
1300
- Object.defineProperty(exports, "eslint$18", {
1301
- enumerable: true,
1302
- get: function() {
1303
- return eslint$18;
1304
- }
1305
- });
1306
- Object.defineProperty(exports, "eslint$19", {
1307
- enumerable: true,
1308
- get: function() {
1309
- return eslint$19;
1310
- }
1311
- });
1312
- Object.defineProperty(exports, "eslint$2", {
1313
- enumerable: true,
1314
- get: function() {
1315
- return eslint$2;
1316
- }
1317
- });
1318
- Object.defineProperty(exports, "eslint$20", {
1319
- enumerable: true,
1320
- get: function() {
1321
- return eslint$20;
1322
- }
1323
- });
1324
- Object.defineProperty(exports, "eslint$21", {
1325
- enumerable: true,
1326
- get: function() {
1327
- return eslint$21;
1328
- }
1329
- });
1330
- Object.defineProperty(exports, "eslint$22", {
1331
- enumerable: true,
1332
- get: function() {
1333
- return eslint$22;
1334
- }
1335
- });
1336
- Object.defineProperty(exports, "eslint$23", {
1337
- enumerable: true,
1338
- get: function() {
1339
- return eslint$23;
1340
- }
1341
- });
1342
- Object.defineProperty(exports, "eslint$24", {
1343
- enumerable: true,
1344
- get: function() {
1345
- return eslint$24;
1346
- }
1347
- });
1348
- Object.defineProperty(exports, "eslint$25", {
1349
- enumerable: true,
1350
- get: function() {
1351
- return eslint$25;
1352
- }
1353
- });
1354
- Object.defineProperty(exports, "eslint$3", {
1355
- enumerable: true,
1356
- get: function() {
1357
- return eslint$3;
1358
- }
1359
- });
1360
- Object.defineProperty(exports, "eslint$4", {
1361
- enumerable: true,
1362
- get: function() {
1363
- return eslint$4;
1364
- }
1365
- });
1366
- Object.defineProperty(exports, "eslint$5", {
1367
- enumerable: true,
1368
- get: function() {
1369
- return eslint$5;
1370
- }
1371
- });
1372
- Object.defineProperty(exports, "eslint$6", {
1373
- enumerable: true,
1374
- get: function() {
1375
- return eslint$6;
1376
- }
1377
- });
1378
- Object.defineProperty(exports, "eslint$7", {
1379
- enumerable: true,
1380
- get: function() {
1381
- return eslint$7;
1382
- }
1383
- });
1384
- Object.defineProperty(exports, "eslint$8", {
1385
- enumerable: true,
1386
- get: function() {
1387
- return eslint$8;
1388
- }
1389
- });
1390
- Object.defineProperty(exports, "eslint$9", {
1391
- enumerable: true,
1392
- get: function() {
1393
- return eslint$9;
1394
- }
1395
- });
1396
- Object.defineProperty(exports, "name", {
1397
- enumerable: true,
1398
- get: function() {
1399
- return name;
1400
- }
1401
- });
1402
- Object.defineProperty(exports, "version", {
1403
- enumerable: true,
1404
- get: function() {
1405
- return version;
1406
- }
1407
- });