@fedify/lint 2.4.0-dev.1988 → 2.4.0-dev.2010
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/deno.json +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/{outbox-listener-delivery-required-BGidqeKp.js → outbox-listener-delivery-required-DzoYEMe6.js} +395 -8
- package/dist/{outbox-listener-delivery-required-DwagvcVS.cjs → outbox-listener-delivery-required-glWw7_mh.cjs} +395 -8
- package/dist/oxlint.cjs +1 -1
- package/dist/oxlint.js +1 -1
- package/package.json +3 -3
package/deno.json
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
enumerable: true
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
//#endregion
|
|
27
|
-
const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-
|
|
27
|
+
const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-glWw7_mh.cjs");
|
|
28
28
|
let _fxts_core = require("@fxts/core");
|
|
29
29
|
let _typescript_eslint_parser = require("@typescript-eslint/parser");
|
|
30
30
|
_typescript_eslint_parser = __toESM(_typescript_eslint_parser, 1);
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as version, C as eslint$12, D as eslint$21, E as eslint$11, O as RULE_IDS, S as eslint$5, T as eslint$10, _ as eslint$1, a as eslint$18, b as eslint$3, c as eslint$17, d as eslint$6, f as eslint$7, g as eslint$15, h as eslint$14, i as eslint$23, k as name, l as eslint$20, m as eslint$9, n as eslint$25, o as eslint$19, p as eslint$8, r as eslint$26, s as eslint$16, t as eslint$24, u as eslint$22, v as eslint, w as eslint$13, x as eslint$4, y as eslint$2 } from "./outbox-listener-delivery-required-
|
|
1
|
+
import { A as version, C as eslint$12, D as eslint$21, E as eslint$11, O as RULE_IDS, S as eslint$5, T as eslint$10, _ as eslint$1, a as eslint$18, b as eslint$3, c as eslint$17, d as eslint$6, f as eslint$7, g as eslint$15, h as eslint$14, i as eslint$23, k as name, l as eslint$20, m as eslint$9, n as eslint$25, o as eslint$19, p as eslint$8, r as eslint$26, s as eslint$16, t as eslint$24, u as eslint$22, v as eslint, w as eslint$13, x as eslint$4, y as eslint$2 } from "./outbox-listener-delivery-required-DzoYEMe6.js";
|
|
2
2
|
import { fromEntries, keys, map, pipe } from "@fxts/core";
|
|
3
3
|
import parser from "@typescript-eslint/parser";
|
|
4
4
|
//#region src/index.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { always, every, head, isEmpty, isObject, negate, pipe, pipeLazy, prop, some, toArray, unless, when } from "@fxts/core";
|
|
2
2
|
//#region deno.json
|
|
3
3
|
var name = "@fedify/lint";
|
|
4
|
-
var version = "2.4.0-dev.
|
|
4
|
+
var version = "2.4.0-dev.2010+498229f4";
|
|
5
5
|
//#endregion
|
|
6
6
|
//#region src/lib/const.ts
|
|
7
7
|
/**
|
|
@@ -744,7 +744,7 @@ function getContextParamName(callback) {
|
|
|
744
744
|
const contextParam = unwrapAssignmentPattern(callback.params[0]);
|
|
745
745
|
return contextParam?.type === "Identifier" ? contextParam.name : null;
|
|
746
746
|
}
|
|
747
|
-
const FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
747
|
+
const FUNCTION_NODE_TYPES$1 = /* @__PURE__ */ new Set([
|
|
748
748
|
"FunctionDeclaration",
|
|
749
749
|
"FunctionExpression",
|
|
750
750
|
"ArrowFunctionExpression"
|
|
@@ -762,7 +762,7 @@ function collectLocalBindings(node, bindings) {
|
|
|
762
762
|
}
|
|
763
763
|
const n = node;
|
|
764
764
|
if (typeof n.type !== "string") return;
|
|
765
|
-
if (FUNCTION_NODE_TYPES.has(n.type)) return;
|
|
765
|
+
if (FUNCTION_NODE_TYPES$1.has(n.type)) return;
|
|
766
766
|
if (n.type === "VariableDeclarator") {
|
|
767
767
|
const id = n.id;
|
|
768
768
|
if (id != null && isNode(id) && id.type === "Identifier" && n.init != null) {
|
|
@@ -796,7 +796,7 @@ function collectReturnedExpressions(callback) {
|
|
|
796
796
|
result.push(n.argument ?? null);
|
|
797
797
|
return;
|
|
798
798
|
}
|
|
799
|
-
if (FUNCTION_NODE_TYPES.has(n.type)) return;
|
|
799
|
+
if (FUNCTION_NODE_TYPES$1.has(n.type)) return;
|
|
800
800
|
for (const key in n) {
|
|
801
801
|
if (key === "parent") continue;
|
|
802
802
|
walk(n[key]);
|
|
@@ -1021,6 +1021,12 @@ const isChainedFromOutboxListeners = (expr, federationTracker) => {
|
|
|
1021
1021
|
return false;
|
|
1022
1022
|
};
|
|
1023
1023
|
const DELIVERY_METHOD_NAMES = /* @__PURE__ */ new Set(["sendActivity", "forwardActivity"]);
|
|
1024
|
+
const FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
1025
|
+
"FunctionDeclaration",
|
|
1026
|
+
"FunctionExpression",
|
|
1027
|
+
"ArrowFunctionExpression"
|
|
1028
|
+
]);
|
|
1029
|
+
const isFunctionLikeNode = (node) => FUNCTION_NODE_TYPES.has(node.type);
|
|
1024
1030
|
const getMemberPropertyName = (expr) => {
|
|
1025
1031
|
if (expr.type !== "MemberExpression") return null;
|
|
1026
1032
|
const property = expr.property;
|
|
@@ -1160,7 +1166,14 @@ function buildContextExpressionPattern(contextName) {
|
|
|
1160
1166
|
const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
|
|
1161
1167
|
return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
|
|
1162
1168
|
}
|
|
1163
|
-
|
|
1169
|
+
/**
|
|
1170
|
+
* Resolves an expression to the function it refers to: a direct function
|
|
1171
|
+
* literal, a local variable bound to one, or a property of a local object
|
|
1172
|
+
* literal bound to one (e.g. `handlers.deliver` where
|
|
1173
|
+
* `const handlers = { deliver() {} }`). Used to resolve a listener argument
|
|
1174
|
+
* (`.on(Activity, handler)`).
|
|
1175
|
+
*/
|
|
1176
|
+
const resolveFunctionBinding = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
|
|
1164
1177
|
if (isFunction(expr)) return expr;
|
|
1165
1178
|
if (expr.type === "Identifier") {
|
|
1166
1179
|
if (seen.has(expr.name)) return null;
|
|
@@ -1168,7 +1181,7 @@ const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set
|
|
|
1168
1181
|
const binding = bindings.get(expr.name);
|
|
1169
1182
|
if (binding == null || !isNode(binding)) return null;
|
|
1170
1183
|
if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
|
|
1171
|
-
if (binding.type === "Identifier") return
|
|
1184
|
+
if (binding.type === "Identifier") return resolveFunctionBinding(binding, bindings, seen);
|
|
1172
1185
|
return null;
|
|
1173
1186
|
}
|
|
1174
1187
|
if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
|
|
@@ -1185,8 +1198,382 @@ const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set
|
|
|
1185
1198
|
}
|
|
1186
1199
|
return null;
|
|
1187
1200
|
};
|
|
1201
|
+
const isStaticallyFalsy = (test) => test.type === "Literal" && !test.value;
|
|
1202
|
+
const isStaticallyTruthy = (test) => test.type === "Literal" && Boolean(test.value);
|
|
1203
|
+
/**
|
|
1204
|
+
* Whether every path through this statement unconditionally returns or
|
|
1205
|
+
* throws, meaning anything textually after it in the same statement list
|
|
1206
|
+
* never runs. Deliberately conservative: when it can't prove that, it
|
|
1207
|
+
* answers `false`, which keeps the following code counted as reachable
|
|
1208
|
+
* (a missed dead-code case is safer than wrongly hiding live code).
|
|
1209
|
+
*/
|
|
1210
|
+
function alwaysExits(node) {
|
|
1211
|
+
switch (node.type) {
|
|
1212
|
+
case "ReturnStatement":
|
|
1213
|
+
case "ThrowStatement":
|
|
1214
|
+
case "BreakStatement":
|
|
1215
|
+
case "ContinueStatement": return true;
|
|
1216
|
+
case "BlockStatement": return node.body.some((statement) => alwaysExits(statement));
|
|
1217
|
+
case "IfStatement": {
|
|
1218
|
+
const test = node.test;
|
|
1219
|
+
if (isStaticallyFalsy(test)) return node.alternate != null && alwaysExits(node.alternate);
|
|
1220
|
+
if (isStaticallyTruthy(test)) return alwaysExits(node.consequent);
|
|
1221
|
+
if (node.alternate == null) return false;
|
|
1222
|
+
return alwaysExits(node.consequent) && alwaysExits(node.alternate);
|
|
1223
|
+
}
|
|
1224
|
+
case "TryStatement": return node.finalizer != null && alwaysExits(node.finalizer);
|
|
1225
|
+
default: return false;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
function collectReachableStatements(node, out) {
|
|
1229
|
+
switch (node.type) {
|
|
1230
|
+
case "BlockStatement":
|
|
1231
|
+
for (const [index, statement] of node.body.entries()) {
|
|
1232
|
+
collectReachableStatements(statement, out);
|
|
1233
|
+
if (alwaysExits(statement)) {
|
|
1234
|
+
for (const rest of node.body.slice(index + 1)) if (rest.type === "FunctionDeclaration") out.push(rest);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return;
|
|
1239
|
+
case "IfStatement": {
|
|
1240
|
+
const test = node.test;
|
|
1241
|
+
out.push(test);
|
|
1242
|
+
if (!isStaticallyFalsy(test)) collectReachableStatements(node.consequent, out);
|
|
1243
|
+
if (node.alternate != null && !isStaticallyTruthy(test)) collectReachableStatements(node.alternate, out);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
case "TryStatement":
|
|
1247
|
+
collectReachableStatements(node.block, out);
|
|
1248
|
+
if (node.handler != null) collectReachableStatements(node.handler.body, out);
|
|
1249
|
+
if (node.finalizer != null) collectReachableStatements(node.finalizer, out);
|
|
1250
|
+
return;
|
|
1251
|
+
case "SwitchStatement":
|
|
1252
|
+
out.push(node.discriminant);
|
|
1253
|
+
for (const switchCase of node.cases) {
|
|
1254
|
+
if (switchCase.test != null) out.push(switchCase.test);
|
|
1255
|
+
for (const statement of switchCase.consequent) {
|
|
1256
|
+
collectReachableStatements(statement, out);
|
|
1257
|
+
if (alwaysExits(statement)) break;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return;
|
|
1261
|
+
case "WhileStatement":
|
|
1262
|
+
case "DoWhileStatement":
|
|
1263
|
+
out.push(node.test);
|
|
1264
|
+
collectReachableStatements(node.body, out);
|
|
1265
|
+
return;
|
|
1266
|
+
case "ForStatement":
|
|
1267
|
+
for (const head of [
|
|
1268
|
+
node.init,
|
|
1269
|
+
node.test,
|
|
1270
|
+
node.update
|
|
1271
|
+
]) if (head != null) out.push(head);
|
|
1272
|
+
collectReachableStatements(node.body, out);
|
|
1273
|
+
return;
|
|
1274
|
+
case "ForInStatement":
|
|
1275
|
+
case "ForOfStatement":
|
|
1276
|
+
out.push(node.right);
|
|
1277
|
+
collectReachableStatements(node.body, out);
|
|
1278
|
+
return;
|
|
1279
|
+
case "LabeledStatement":
|
|
1280
|
+
collectReachableStatements(node.body, out);
|
|
1281
|
+
return;
|
|
1282
|
+
case "WithStatement":
|
|
1283
|
+
out.push(node.object);
|
|
1284
|
+
collectReachableStatements(node.body, out);
|
|
1285
|
+
return;
|
|
1286
|
+
default:
|
|
1287
|
+
out.push(node);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Collects plain-value references to identifiers: `deliver()`,
|
|
1293
|
+
* `forEach(deliver)`, a shorthand `{ deliver }`, and so on. Skips positions
|
|
1294
|
+
* that name something rather than reference a value: a declaration's own
|
|
1295
|
+
* `id`/params, the non-computed `.property` of a member expression (so
|
|
1296
|
+
* `someService.deliver()` never counts as a reference to an unrelated local
|
|
1297
|
+
* `deliver`), and the target of an assignment, which writes to a name
|
|
1298
|
+
* instead of reading it.
|
|
1299
|
+
*/
|
|
1300
|
+
function collectReferencedNames(node, out) {
|
|
1301
|
+
if (node == null || typeof node !== "object") return;
|
|
1302
|
+
if (Array.isArray(node)) {
|
|
1303
|
+
for (const item of node) collectReferencedNames(item, out);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (!isNode(node)) return;
|
|
1307
|
+
const n = node;
|
|
1308
|
+
if (n.type === "Identifier") {
|
|
1309
|
+
out.add(n.name);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
if (n.type === "MemberExpression" && !n.computed) {
|
|
1313
|
+
collectReferencedNames(n.object, out);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (n.type === "Property" && !n.computed) {
|
|
1317
|
+
collectReferencedNames(n.value, out);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (n.type === "VariableDeclarator") {
|
|
1321
|
+
if (n.init != null) collectReferencedNames(n.init, out);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (n.type === "ClassDeclaration" || n.type === "ClassExpression") {
|
|
1325
|
+
collectReferencedNames(n.superClass, out);
|
|
1326
|
+
collectReferencedNames(n.body, out);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if ((n.type === "MethodDefinition" || n.type === "PropertyDefinition") && !n.computed) {
|
|
1330
|
+
collectReferencedNames(n.value, out);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (n.type === "AssignmentExpression" && n.operator === "=") {
|
|
1334
|
+
if (getAssignmentTargetName(n.left) != null) {
|
|
1335
|
+
collectReferencedNames(n.right, out);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (isFunctionLikeNode(n)) return;
|
|
1340
|
+
const record = n;
|
|
1341
|
+
for (const key in record) {
|
|
1342
|
+
if (key === "parent") continue;
|
|
1343
|
+
collectReferencedNames(record[key], out);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* The name an assignment writes to: `x` for `x = ...`, and the root object
|
|
1348
|
+
* for `obj.a.b = ...`. `null` for anything more exotic.
|
|
1349
|
+
*/
|
|
1350
|
+
function getAssignmentTargetName(target) {
|
|
1351
|
+
let current = target;
|
|
1352
|
+
while (current.type === "MemberExpression") current = current.object;
|
|
1353
|
+
return current.type === "Identifier" ? current.name : null;
|
|
1354
|
+
}
|
|
1355
|
+
/** Every identifier a declaration pattern binds (`a`, `{ a, b: c }`, `[a]`). */
|
|
1356
|
+
function collectBoundNames(pattern, out) {
|
|
1357
|
+
if (pattern == null || typeof pattern !== "object" || !isNode(pattern)) return;
|
|
1358
|
+
const p = pattern;
|
|
1359
|
+
switch (p.type) {
|
|
1360
|
+
case "Identifier":
|
|
1361
|
+
out.push(p.name);
|
|
1362
|
+
return;
|
|
1363
|
+
case "AssignmentPattern":
|
|
1364
|
+
collectBoundNames(p.left, out);
|
|
1365
|
+
return;
|
|
1366
|
+
case "RestElement":
|
|
1367
|
+
collectBoundNames(p.argument, out);
|
|
1368
|
+
return;
|
|
1369
|
+
case "ArrayPattern":
|
|
1370
|
+
for (const element of p.elements) collectBoundNames(element, out);
|
|
1371
|
+
return;
|
|
1372
|
+
case "ObjectPattern":
|
|
1373
|
+
for (const prop of p.properties) collectBoundNames(prop.value ?? prop.argument, out);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Finds the function literals a value holds itself: the value is the
|
|
1379
|
+
* function, or it sits in an object or array literal, a conditional, or a
|
|
1380
|
+
* class body. Stops at a call, so a function handed to one as an argument,
|
|
1381
|
+
* or invoked by it, is not held by whatever the call's result is bound to.
|
|
1382
|
+
* Those count on their own wherever they appear.
|
|
1383
|
+
*/
|
|
1384
|
+
function collectHeldFunctions(node, out) {
|
|
1385
|
+
if (node == null || typeof node !== "object") return;
|
|
1386
|
+
if (Array.isArray(node)) {
|
|
1387
|
+
for (const item of node) collectHeldFunctions(item, out);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (!isNode(node)) return;
|
|
1391
|
+
const n = node;
|
|
1392
|
+
if (isFunctionLikeNode(n)) {
|
|
1393
|
+
out.push(n);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (n.type === "CallExpression" || n.type === "NewExpression") return;
|
|
1397
|
+
const record = n;
|
|
1398
|
+
for (const key in record) {
|
|
1399
|
+
if (key === "parent") continue;
|
|
1400
|
+
collectHeldFunctions(record[key], out);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Collects the functions each name in `node`'s own scope holds: `function
|
|
1405
|
+
* name() {}`, `class Name {}`, and the value of `const name = ...` or a
|
|
1406
|
+
* later `name = ...` or `name.prop = ...`, including a function inside an
|
|
1407
|
+
* object or array literal (see `collectHeldFunctions`). A function held
|
|
1408
|
+
* under a name counts as used as soon as the name is mentioned, however it
|
|
1409
|
+
* is mentioned, so this never has to work out how the name reaches the
|
|
1410
|
+
* function. Does not descend into a found function's own body: a name bound
|
|
1411
|
+
* inside it is only found once that function is itself resolved as
|
|
1412
|
+
* reachable, so it can be layered on top of (and correctly shadow) the outer
|
|
1413
|
+
* scope's names.
|
|
1414
|
+
*/
|
|
1415
|
+
function collectFunctionsByName(node, out) {
|
|
1416
|
+
if (node == null || typeof node !== "object") return;
|
|
1417
|
+
if (Array.isArray(node)) {
|
|
1418
|
+
for (const item of node) collectFunctionsByName(item, out);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (!isNode(node)) return;
|
|
1422
|
+
const n = node;
|
|
1423
|
+
const bindTo = (names, from) => {
|
|
1424
|
+
const functions = [];
|
|
1425
|
+
collectHeldFunctions(from, functions);
|
|
1426
|
+
if (functions.length < 1) return;
|
|
1427
|
+
for (const name of names) out.set(name, [...out.get(name) ?? [], ...functions]);
|
|
1428
|
+
};
|
|
1429
|
+
if (n.type === "FunctionDeclaration") {
|
|
1430
|
+
if (n.id?.name != null) bindTo([n.id.name], n);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (n.type === "ClassDeclaration") {
|
|
1434
|
+
if (n.id?.name != null) bindTo([n.id.name], n);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (isFunctionLikeNode(n)) return;
|
|
1438
|
+
if (n.type === "VariableDeclarator") {
|
|
1439
|
+
const decl = n;
|
|
1440
|
+
if (decl.init != null) {
|
|
1441
|
+
const names = [];
|
|
1442
|
+
collectBoundNames(decl.id, names);
|
|
1443
|
+
bindTo(names, decl.init);
|
|
1444
|
+
collectFunctionsByName(decl.init, out);
|
|
1445
|
+
}
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (n.type === "AssignmentExpression") {
|
|
1449
|
+
const name = getAssignmentTargetName(n.left);
|
|
1450
|
+
if (name != null) bindTo([name], n.right);
|
|
1451
|
+
collectFunctionsByName(n.right, out);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const record = n;
|
|
1455
|
+
for (const key in record) {
|
|
1456
|
+
if (key === "parent") continue;
|
|
1457
|
+
collectFunctionsByName(record[key], out);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Finds function literals directly nested in a reachable statement, without
|
|
1462
|
+
* descending past them -- their own reachability is decided separately.
|
|
1463
|
+
*/
|
|
1464
|
+
function collectNestedFunctions(node, out) {
|
|
1465
|
+
if (node == null || typeof node !== "object") return;
|
|
1466
|
+
if (Array.isArray(node)) {
|
|
1467
|
+
for (const item of node) collectNestedFunctions(item, out);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (!isNode(node)) return;
|
|
1471
|
+
const n = node;
|
|
1472
|
+
if (isFunctionLikeNode(n)) {
|
|
1473
|
+
out.push(n);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const record = n;
|
|
1477
|
+
for (const key in record) {
|
|
1478
|
+
if (key === "parent") continue;
|
|
1479
|
+
collectNestedFunctions(record[key], out);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Computes the full set of function nodes that are actually reachable from
|
|
1484
|
+
* `root`: `root` itself feeds a worklist, and each function it (or a
|
|
1485
|
+
* function already on the worklist) uses from *its own* reachable
|
|
1486
|
+
* statements -- never from a dead branch or some other not-yet-reached
|
|
1487
|
+
* function's body -- gets queued in turn. `outerFunctionsByName` is layered
|
|
1488
|
+
* fresh for each scope, so a name bound at an inner scope shadows a
|
|
1489
|
+
* same-named one further out instead of overwriting it globally, and a
|
|
1490
|
+
* dead branch that merely mentions a name never queues what it holds.
|
|
1491
|
+
*/
|
|
1492
|
+
function computeUsedFunctions(root) {
|
|
1493
|
+
const used = /* @__PURE__ */ new Set();
|
|
1494
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1495
|
+
const processScope = (scopeRoot, outerFunctionsByName) => {
|
|
1496
|
+
if (visited.has(scopeRoot)) return;
|
|
1497
|
+
visited.add(scopeRoot);
|
|
1498
|
+
const statements = [];
|
|
1499
|
+
collectReachableStatements(scopeRoot, statements);
|
|
1500
|
+
const functionsHere = /* @__PURE__ */ new Map();
|
|
1501
|
+
for (const statement of statements) collectFunctionsByName(statement, functionsHere);
|
|
1502
|
+
const functionsByName = new Map(outerFunctionsByName);
|
|
1503
|
+
for (const [name, functions] of functionsHere) functionsByName.set(name, functions);
|
|
1504
|
+
const referencedNames = /* @__PURE__ */ new Set();
|
|
1505
|
+
for (const statement of statements) collectReferencedNames(statement, referencedNames);
|
|
1506
|
+
const reached = /* @__PURE__ */ new Set();
|
|
1507
|
+
for (const [name, functions] of functionsByName) {
|
|
1508
|
+
if (!referencedNames.has(name)) continue;
|
|
1509
|
+
for (const fn of functions) reached.add(fn);
|
|
1510
|
+
}
|
|
1511
|
+
const held = /* @__PURE__ */ new Set();
|
|
1512
|
+
for (const functions of functionsHere.values()) for (const fn of functions) held.add(fn);
|
|
1513
|
+
for (const statement of statements) {
|
|
1514
|
+
const nested = [];
|
|
1515
|
+
collectNestedFunctions(statement, nested);
|
|
1516
|
+
for (const fn of nested) if (!held.has(fn)) reached.add(fn);
|
|
1517
|
+
}
|
|
1518
|
+
for (const fn of reached) {
|
|
1519
|
+
used.add(fn);
|
|
1520
|
+
processScope(fn.body, functionsByName);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
processScope(root, /* @__PURE__ */ new Map());
|
|
1524
|
+
return used;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* A node's `[start, end)` character offsets into the whole source file.
|
|
1528
|
+
* Both engines always populate this -- ESLint forces it on regardless of
|
|
1529
|
+
* parser options, and Deno.lint exposes it the same way as every other
|
|
1530
|
+
* child property (see the `for...in` note on why plain property access
|
|
1531
|
+
* still works even though it's not an own enumerable property).
|
|
1532
|
+
*/
|
|
1533
|
+
function getRange(node) {
|
|
1534
|
+
return node.range;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Builds the source text to scan for a delivery call: the reachable
|
|
1538
|
+
* statements of `root`, with every nested function literal either folded
|
|
1539
|
+
* in (its own reachable text spliced in place, wherever that function's
|
|
1540
|
+
* own declaration happens to live) or blanked out, depending on whether
|
|
1541
|
+
* `used` (from `computeUsedFunctions`) says it is actually invoked.
|
|
1542
|
+
*
|
|
1543
|
+
* Splices each function by its own range rather than by matching its
|
|
1544
|
+
* source text, and applies the splices from the end of the statement
|
|
1545
|
+
* backward. That keeps two functions with byte-identical bodies (e.g. two
|
|
1546
|
+
* object-literal methods that both merely call `ctx.sendActivity(...)`)
|
|
1547
|
+
* from colliding: a text-based replacement would find and blank out both
|
|
1548
|
+
* occurrences the first time either one is processed, since it matches by
|
|
1549
|
+
* content everywhere in the statement rather than by which node is
|
|
1550
|
+
* actually being replaced. Replacing from the end backward also means a
|
|
1551
|
+
* later replacement's length change never shifts the still-unprocessed
|
|
1552
|
+
* offsets of an earlier one.
|
|
1553
|
+
*/
|
|
1554
|
+
function collectDeliveryScanCode(sourceCode, root, used, visited) {
|
|
1555
|
+
if (visited.has(root)) return "";
|
|
1556
|
+
visited.add(root);
|
|
1557
|
+
const statements = [];
|
|
1558
|
+
collectReachableStatements(root, statements);
|
|
1559
|
+
return statements.map((statement) => {
|
|
1560
|
+
const text = sourceCode.getText(statement);
|
|
1561
|
+
const [statementStart] = getRange(statement);
|
|
1562
|
+
const nested = [];
|
|
1563
|
+
collectNestedFunctions(statement, nested);
|
|
1564
|
+
const byDescendingStart = [...nested].sort((a, b) => getRange(b)[0] - getRange(a)[0]);
|
|
1565
|
+
let result = text;
|
|
1566
|
+
for (const fn of byDescendingStart) {
|
|
1567
|
+
const [fnStart, fnEnd] = getRange(fn);
|
|
1568
|
+
const replacement = used.has(fn) ? collectDeliveryScanCode(sourceCode, fn.body, used, visited) : "";
|
|
1569
|
+
result = result.slice(0, fnStart - statementStart) + (replacement.length > 0 ? replacement : "()=>{}") + result.slice(fnEnd - statementStart);
|
|
1570
|
+
}
|
|
1571
|
+
return result;
|
|
1572
|
+
}).join("\n");
|
|
1573
|
+
}
|
|
1188
1574
|
const listenerCallsDeliveryMethod = (sourceCode, listener) => {
|
|
1189
|
-
const
|
|
1575
|
+
const used = computeUsedFunctions(listener.body);
|
|
1576
|
+
const code = stripCommentsAndStrings(collectDeliveryScanCode(sourceCode, listener.body, used, /* @__PURE__ */ new Set()));
|
|
1190
1577
|
const aliases = /* @__PURE__ */ new Set();
|
|
1191
1578
|
const contextParam = unwrapContextParam(listener.params[0]);
|
|
1192
1579
|
const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
|
|
@@ -1224,7 +1611,7 @@ function createRule(buildReport) {
|
|
|
1224
1611
|
if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
|
|
1225
1612
|
if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
|
|
1226
1613
|
const listener = node.arguments[1];
|
|
1227
|
-
const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ?
|
|
1614
|
+
const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveFunctionBinding(listener, bindings) : null;
|
|
1228
1615
|
if (resolvedListener == null) return;
|
|
1229
1616
|
if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
|
|
1230
1617
|
context.report({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
let _fxts_core = require("@fxts/core");
|
|
2
2
|
//#region deno.json
|
|
3
3
|
var name = "@fedify/lint";
|
|
4
|
-
var version = "2.4.0-dev.
|
|
4
|
+
var version = "2.4.0-dev.2010+498229f4";
|
|
5
5
|
//#endregion
|
|
6
6
|
//#region src/lib/const.ts
|
|
7
7
|
/**
|
|
@@ -744,7 +744,7 @@ function getContextParamName(callback) {
|
|
|
744
744
|
const contextParam = unwrapAssignmentPattern(callback.params[0]);
|
|
745
745
|
return contextParam?.type === "Identifier" ? contextParam.name : null;
|
|
746
746
|
}
|
|
747
|
-
const FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
747
|
+
const FUNCTION_NODE_TYPES$1 = /* @__PURE__ */ new Set([
|
|
748
748
|
"FunctionDeclaration",
|
|
749
749
|
"FunctionExpression",
|
|
750
750
|
"ArrowFunctionExpression"
|
|
@@ -762,7 +762,7 @@ function collectLocalBindings(node, bindings) {
|
|
|
762
762
|
}
|
|
763
763
|
const n = node;
|
|
764
764
|
if (typeof n.type !== "string") return;
|
|
765
|
-
if (FUNCTION_NODE_TYPES.has(n.type)) return;
|
|
765
|
+
if (FUNCTION_NODE_TYPES$1.has(n.type)) return;
|
|
766
766
|
if (n.type === "VariableDeclarator") {
|
|
767
767
|
const id = n.id;
|
|
768
768
|
if (id != null && isNode(id) && id.type === "Identifier" && n.init != null) {
|
|
@@ -796,7 +796,7 @@ function collectReturnedExpressions(callback) {
|
|
|
796
796
|
result.push(n.argument ?? null);
|
|
797
797
|
return;
|
|
798
798
|
}
|
|
799
|
-
if (FUNCTION_NODE_TYPES.has(n.type)) return;
|
|
799
|
+
if (FUNCTION_NODE_TYPES$1.has(n.type)) return;
|
|
800
800
|
for (const key in n) {
|
|
801
801
|
if (key === "parent") continue;
|
|
802
802
|
walk(n[key]);
|
|
@@ -1021,6 +1021,12 @@ const isChainedFromOutboxListeners = (expr, federationTracker) => {
|
|
|
1021
1021
|
return false;
|
|
1022
1022
|
};
|
|
1023
1023
|
const DELIVERY_METHOD_NAMES = /* @__PURE__ */ new Set(["sendActivity", "forwardActivity"]);
|
|
1024
|
+
const FUNCTION_NODE_TYPES = /* @__PURE__ */ new Set([
|
|
1025
|
+
"FunctionDeclaration",
|
|
1026
|
+
"FunctionExpression",
|
|
1027
|
+
"ArrowFunctionExpression"
|
|
1028
|
+
]);
|
|
1029
|
+
const isFunctionLikeNode = (node) => FUNCTION_NODE_TYPES.has(node.type);
|
|
1024
1030
|
const getMemberPropertyName = (expr) => {
|
|
1025
1031
|
if (expr.type !== "MemberExpression") return null;
|
|
1026
1032
|
const property = expr.property;
|
|
@@ -1160,7 +1166,14 @@ function buildContextExpressionPattern(contextName) {
|
|
|
1160
1166
|
const boundedName = String.raw`(?<![\w$])${name}(?![\w$])`;
|
|
1161
1167
|
return String.raw`(?:${boundedName}|\(\s*${boundedName}(?:\s+as\s+[^)]+)?\s*\))`;
|
|
1162
1168
|
}
|
|
1163
|
-
|
|
1169
|
+
/**
|
|
1170
|
+
* Resolves an expression to the function it refers to: a direct function
|
|
1171
|
+
* literal, a local variable bound to one, or a property of a local object
|
|
1172
|
+
* literal bound to one (e.g. `handlers.deliver` where
|
|
1173
|
+
* `const handlers = { deliver() {} }`). Used to resolve a listener argument
|
|
1174
|
+
* (`.on(Activity, handler)`).
|
|
1175
|
+
*/
|
|
1176
|
+
const resolveFunctionBinding = (expr, bindings, seen = /* @__PURE__ */ new Set()) => {
|
|
1164
1177
|
if (isFunction(expr)) return expr;
|
|
1165
1178
|
if (expr.type === "Identifier") {
|
|
1166
1179
|
if (seen.has(expr.name)) return null;
|
|
@@ -1168,7 +1181,7 @@ const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set
|
|
|
1168
1181
|
const binding = bindings.get(expr.name);
|
|
1169
1182
|
if (binding == null || !isNode(binding)) return null;
|
|
1170
1183
|
if (isFunction(binding) || binding.type === "FunctionDeclaration") return binding;
|
|
1171
|
-
if (binding.type === "Identifier") return
|
|
1184
|
+
if (binding.type === "Identifier") return resolveFunctionBinding(binding, bindings, seen);
|
|
1172
1185
|
return null;
|
|
1173
1186
|
}
|
|
1174
1187
|
if (expr.type === "MemberExpression" && expr.object.type === "Identifier" && !expr.computed) {
|
|
@@ -1185,8 +1198,382 @@ const resolveListenerReference = (expr, bindings, seen = /* @__PURE__ */ new Set
|
|
|
1185
1198
|
}
|
|
1186
1199
|
return null;
|
|
1187
1200
|
};
|
|
1201
|
+
const isStaticallyFalsy = (test) => test.type === "Literal" && !test.value;
|
|
1202
|
+
const isStaticallyTruthy = (test) => test.type === "Literal" && Boolean(test.value);
|
|
1203
|
+
/**
|
|
1204
|
+
* Whether every path through this statement unconditionally returns or
|
|
1205
|
+
* throws, meaning anything textually after it in the same statement list
|
|
1206
|
+
* never runs. Deliberately conservative: when it can't prove that, it
|
|
1207
|
+
* answers `false`, which keeps the following code counted as reachable
|
|
1208
|
+
* (a missed dead-code case is safer than wrongly hiding live code).
|
|
1209
|
+
*/
|
|
1210
|
+
function alwaysExits(node) {
|
|
1211
|
+
switch (node.type) {
|
|
1212
|
+
case "ReturnStatement":
|
|
1213
|
+
case "ThrowStatement":
|
|
1214
|
+
case "BreakStatement":
|
|
1215
|
+
case "ContinueStatement": return true;
|
|
1216
|
+
case "BlockStatement": return node.body.some((statement) => alwaysExits(statement));
|
|
1217
|
+
case "IfStatement": {
|
|
1218
|
+
const test = node.test;
|
|
1219
|
+
if (isStaticallyFalsy(test)) return node.alternate != null && alwaysExits(node.alternate);
|
|
1220
|
+
if (isStaticallyTruthy(test)) return alwaysExits(node.consequent);
|
|
1221
|
+
if (node.alternate == null) return false;
|
|
1222
|
+
return alwaysExits(node.consequent) && alwaysExits(node.alternate);
|
|
1223
|
+
}
|
|
1224
|
+
case "TryStatement": return node.finalizer != null && alwaysExits(node.finalizer);
|
|
1225
|
+
default: return false;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
function collectReachableStatements(node, out) {
|
|
1229
|
+
switch (node.type) {
|
|
1230
|
+
case "BlockStatement":
|
|
1231
|
+
for (const [index, statement] of node.body.entries()) {
|
|
1232
|
+
collectReachableStatements(statement, out);
|
|
1233
|
+
if (alwaysExits(statement)) {
|
|
1234
|
+
for (const rest of node.body.slice(index + 1)) if (rest.type === "FunctionDeclaration") out.push(rest);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return;
|
|
1239
|
+
case "IfStatement": {
|
|
1240
|
+
const test = node.test;
|
|
1241
|
+
out.push(test);
|
|
1242
|
+
if (!isStaticallyFalsy(test)) collectReachableStatements(node.consequent, out);
|
|
1243
|
+
if (node.alternate != null && !isStaticallyTruthy(test)) collectReachableStatements(node.alternate, out);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
case "TryStatement":
|
|
1247
|
+
collectReachableStatements(node.block, out);
|
|
1248
|
+
if (node.handler != null) collectReachableStatements(node.handler.body, out);
|
|
1249
|
+
if (node.finalizer != null) collectReachableStatements(node.finalizer, out);
|
|
1250
|
+
return;
|
|
1251
|
+
case "SwitchStatement":
|
|
1252
|
+
out.push(node.discriminant);
|
|
1253
|
+
for (const switchCase of node.cases) {
|
|
1254
|
+
if (switchCase.test != null) out.push(switchCase.test);
|
|
1255
|
+
for (const statement of switchCase.consequent) {
|
|
1256
|
+
collectReachableStatements(statement, out);
|
|
1257
|
+
if (alwaysExits(statement)) break;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return;
|
|
1261
|
+
case "WhileStatement":
|
|
1262
|
+
case "DoWhileStatement":
|
|
1263
|
+
out.push(node.test);
|
|
1264
|
+
collectReachableStatements(node.body, out);
|
|
1265
|
+
return;
|
|
1266
|
+
case "ForStatement":
|
|
1267
|
+
for (const head of [
|
|
1268
|
+
node.init,
|
|
1269
|
+
node.test,
|
|
1270
|
+
node.update
|
|
1271
|
+
]) if (head != null) out.push(head);
|
|
1272
|
+
collectReachableStatements(node.body, out);
|
|
1273
|
+
return;
|
|
1274
|
+
case "ForInStatement":
|
|
1275
|
+
case "ForOfStatement":
|
|
1276
|
+
out.push(node.right);
|
|
1277
|
+
collectReachableStatements(node.body, out);
|
|
1278
|
+
return;
|
|
1279
|
+
case "LabeledStatement":
|
|
1280
|
+
collectReachableStatements(node.body, out);
|
|
1281
|
+
return;
|
|
1282
|
+
case "WithStatement":
|
|
1283
|
+
out.push(node.object);
|
|
1284
|
+
collectReachableStatements(node.body, out);
|
|
1285
|
+
return;
|
|
1286
|
+
default:
|
|
1287
|
+
out.push(node);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Collects plain-value references to identifiers: `deliver()`,
|
|
1293
|
+
* `forEach(deliver)`, a shorthand `{ deliver }`, and so on. Skips positions
|
|
1294
|
+
* that name something rather than reference a value: a declaration's own
|
|
1295
|
+
* `id`/params, the non-computed `.property` of a member expression (so
|
|
1296
|
+
* `someService.deliver()` never counts as a reference to an unrelated local
|
|
1297
|
+
* `deliver`), and the target of an assignment, which writes to a name
|
|
1298
|
+
* instead of reading it.
|
|
1299
|
+
*/
|
|
1300
|
+
function collectReferencedNames(node, out) {
|
|
1301
|
+
if (node == null || typeof node !== "object") return;
|
|
1302
|
+
if (Array.isArray(node)) {
|
|
1303
|
+
for (const item of node) collectReferencedNames(item, out);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
if (!isNode(node)) return;
|
|
1307
|
+
const n = node;
|
|
1308
|
+
if (n.type === "Identifier") {
|
|
1309
|
+
out.add(n.name);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
if (n.type === "MemberExpression" && !n.computed) {
|
|
1313
|
+
collectReferencedNames(n.object, out);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
if (n.type === "Property" && !n.computed) {
|
|
1317
|
+
collectReferencedNames(n.value, out);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
if (n.type === "VariableDeclarator") {
|
|
1321
|
+
if (n.init != null) collectReferencedNames(n.init, out);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (n.type === "ClassDeclaration" || n.type === "ClassExpression") {
|
|
1325
|
+
collectReferencedNames(n.superClass, out);
|
|
1326
|
+
collectReferencedNames(n.body, out);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if ((n.type === "MethodDefinition" || n.type === "PropertyDefinition") && !n.computed) {
|
|
1330
|
+
collectReferencedNames(n.value, out);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (n.type === "AssignmentExpression" && n.operator === "=") {
|
|
1334
|
+
if (getAssignmentTargetName(n.left) != null) {
|
|
1335
|
+
collectReferencedNames(n.right, out);
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (isFunctionLikeNode(n)) return;
|
|
1340
|
+
const record = n;
|
|
1341
|
+
for (const key in record) {
|
|
1342
|
+
if (key === "parent") continue;
|
|
1343
|
+
collectReferencedNames(record[key], out);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* The name an assignment writes to: `x` for `x = ...`, and the root object
|
|
1348
|
+
* for `obj.a.b = ...`. `null` for anything more exotic.
|
|
1349
|
+
*/
|
|
1350
|
+
function getAssignmentTargetName(target) {
|
|
1351
|
+
let current = target;
|
|
1352
|
+
while (current.type === "MemberExpression") current = current.object;
|
|
1353
|
+
return current.type === "Identifier" ? current.name : null;
|
|
1354
|
+
}
|
|
1355
|
+
/** Every identifier a declaration pattern binds (`a`, `{ a, b: c }`, `[a]`). */
|
|
1356
|
+
function collectBoundNames(pattern, out) {
|
|
1357
|
+
if (pattern == null || typeof pattern !== "object" || !isNode(pattern)) return;
|
|
1358
|
+
const p = pattern;
|
|
1359
|
+
switch (p.type) {
|
|
1360
|
+
case "Identifier":
|
|
1361
|
+
out.push(p.name);
|
|
1362
|
+
return;
|
|
1363
|
+
case "AssignmentPattern":
|
|
1364
|
+
collectBoundNames(p.left, out);
|
|
1365
|
+
return;
|
|
1366
|
+
case "RestElement":
|
|
1367
|
+
collectBoundNames(p.argument, out);
|
|
1368
|
+
return;
|
|
1369
|
+
case "ArrayPattern":
|
|
1370
|
+
for (const element of p.elements) collectBoundNames(element, out);
|
|
1371
|
+
return;
|
|
1372
|
+
case "ObjectPattern":
|
|
1373
|
+
for (const prop of p.properties) collectBoundNames(prop.value ?? prop.argument, out);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
/**
|
|
1378
|
+
* Finds the function literals a value holds itself: the value is the
|
|
1379
|
+
* function, or it sits in an object or array literal, a conditional, or a
|
|
1380
|
+
* class body. Stops at a call, so a function handed to one as an argument,
|
|
1381
|
+
* or invoked by it, is not held by whatever the call's result is bound to.
|
|
1382
|
+
* Those count on their own wherever they appear.
|
|
1383
|
+
*/
|
|
1384
|
+
function collectHeldFunctions(node, out) {
|
|
1385
|
+
if (node == null || typeof node !== "object") return;
|
|
1386
|
+
if (Array.isArray(node)) {
|
|
1387
|
+
for (const item of node) collectHeldFunctions(item, out);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (!isNode(node)) return;
|
|
1391
|
+
const n = node;
|
|
1392
|
+
if (isFunctionLikeNode(n)) {
|
|
1393
|
+
out.push(n);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
if (n.type === "CallExpression" || n.type === "NewExpression") return;
|
|
1397
|
+
const record = n;
|
|
1398
|
+
for (const key in record) {
|
|
1399
|
+
if (key === "parent") continue;
|
|
1400
|
+
collectHeldFunctions(record[key], out);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Collects the functions each name in `node`'s own scope holds: `function
|
|
1405
|
+
* name() {}`, `class Name {}`, and the value of `const name = ...` or a
|
|
1406
|
+
* later `name = ...` or `name.prop = ...`, including a function inside an
|
|
1407
|
+
* object or array literal (see `collectHeldFunctions`). A function held
|
|
1408
|
+
* under a name counts as used as soon as the name is mentioned, however it
|
|
1409
|
+
* is mentioned, so this never has to work out how the name reaches the
|
|
1410
|
+
* function. Does not descend into a found function's own body: a name bound
|
|
1411
|
+
* inside it is only found once that function is itself resolved as
|
|
1412
|
+
* reachable, so it can be layered on top of (and correctly shadow) the outer
|
|
1413
|
+
* scope's names.
|
|
1414
|
+
*/
|
|
1415
|
+
function collectFunctionsByName(node, out) {
|
|
1416
|
+
if (node == null || typeof node !== "object") return;
|
|
1417
|
+
if (Array.isArray(node)) {
|
|
1418
|
+
for (const item of node) collectFunctionsByName(item, out);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (!isNode(node)) return;
|
|
1422
|
+
const n = node;
|
|
1423
|
+
const bindTo = (names, from) => {
|
|
1424
|
+
const functions = [];
|
|
1425
|
+
collectHeldFunctions(from, functions);
|
|
1426
|
+
if (functions.length < 1) return;
|
|
1427
|
+
for (const name of names) out.set(name, [...out.get(name) ?? [], ...functions]);
|
|
1428
|
+
};
|
|
1429
|
+
if (n.type === "FunctionDeclaration") {
|
|
1430
|
+
if (n.id?.name != null) bindTo([n.id.name], n);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
if (n.type === "ClassDeclaration") {
|
|
1434
|
+
if (n.id?.name != null) bindTo([n.id.name], n);
|
|
1435
|
+
return;
|
|
1436
|
+
}
|
|
1437
|
+
if (isFunctionLikeNode(n)) return;
|
|
1438
|
+
if (n.type === "VariableDeclarator") {
|
|
1439
|
+
const decl = n;
|
|
1440
|
+
if (decl.init != null) {
|
|
1441
|
+
const names = [];
|
|
1442
|
+
collectBoundNames(decl.id, names);
|
|
1443
|
+
bindTo(names, decl.init);
|
|
1444
|
+
collectFunctionsByName(decl.init, out);
|
|
1445
|
+
}
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (n.type === "AssignmentExpression") {
|
|
1449
|
+
const name = getAssignmentTargetName(n.left);
|
|
1450
|
+
if (name != null) bindTo([name], n.right);
|
|
1451
|
+
collectFunctionsByName(n.right, out);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
const record = n;
|
|
1455
|
+
for (const key in record) {
|
|
1456
|
+
if (key === "parent") continue;
|
|
1457
|
+
collectFunctionsByName(record[key], out);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
/**
|
|
1461
|
+
* Finds function literals directly nested in a reachable statement, without
|
|
1462
|
+
* descending past them -- their own reachability is decided separately.
|
|
1463
|
+
*/
|
|
1464
|
+
function collectNestedFunctions(node, out) {
|
|
1465
|
+
if (node == null || typeof node !== "object") return;
|
|
1466
|
+
if (Array.isArray(node)) {
|
|
1467
|
+
for (const item of node) collectNestedFunctions(item, out);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
if (!isNode(node)) return;
|
|
1471
|
+
const n = node;
|
|
1472
|
+
if (isFunctionLikeNode(n)) {
|
|
1473
|
+
out.push(n);
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const record = n;
|
|
1477
|
+
for (const key in record) {
|
|
1478
|
+
if (key === "parent") continue;
|
|
1479
|
+
collectNestedFunctions(record[key], out);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Computes the full set of function nodes that are actually reachable from
|
|
1484
|
+
* `root`: `root` itself feeds a worklist, and each function it (or a
|
|
1485
|
+
* function already on the worklist) uses from *its own* reachable
|
|
1486
|
+
* statements -- never from a dead branch or some other not-yet-reached
|
|
1487
|
+
* function's body -- gets queued in turn. `outerFunctionsByName` is layered
|
|
1488
|
+
* fresh for each scope, so a name bound at an inner scope shadows a
|
|
1489
|
+
* same-named one further out instead of overwriting it globally, and a
|
|
1490
|
+
* dead branch that merely mentions a name never queues what it holds.
|
|
1491
|
+
*/
|
|
1492
|
+
function computeUsedFunctions(root) {
|
|
1493
|
+
const used = /* @__PURE__ */ new Set();
|
|
1494
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1495
|
+
const processScope = (scopeRoot, outerFunctionsByName) => {
|
|
1496
|
+
if (visited.has(scopeRoot)) return;
|
|
1497
|
+
visited.add(scopeRoot);
|
|
1498
|
+
const statements = [];
|
|
1499
|
+
collectReachableStatements(scopeRoot, statements);
|
|
1500
|
+
const functionsHere = /* @__PURE__ */ new Map();
|
|
1501
|
+
for (const statement of statements) collectFunctionsByName(statement, functionsHere);
|
|
1502
|
+
const functionsByName = new Map(outerFunctionsByName);
|
|
1503
|
+
for (const [name, functions] of functionsHere) functionsByName.set(name, functions);
|
|
1504
|
+
const referencedNames = /* @__PURE__ */ new Set();
|
|
1505
|
+
for (const statement of statements) collectReferencedNames(statement, referencedNames);
|
|
1506
|
+
const reached = /* @__PURE__ */ new Set();
|
|
1507
|
+
for (const [name, functions] of functionsByName) {
|
|
1508
|
+
if (!referencedNames.has(name)) continue;
|
|
1509
|
+
for (const fn of functions) reached.add(fn);
|
|
1510
|
+
}
|
|
1511
|
+
const held = /* @__PURE__ */ new Set();
|
|
1512
|
+
for (const functions of functionsHere.values()) for (const fn of functions) held.add(fn);
|
|
1513
|
+
for (const statement of statements) {
|
|
1514
|
+
const nested = [];
|
|
1515
|
+
collectNestedFunctions(statement, nested);
|
|
1516
|
+
for (const fn of nested) if (!held.has(fn)) reached.add(fn);
|
|
1517
|
+
}
|
|
1518
|
+
for (const fn of reached) {
|
|
1519
|
+
used.add(fn);
|
|
1520
|
+
processScope(fn.body, functionsByName);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
processScope(root, /* @__PURE__ */ new Map());
|
|
1524
|
+
return used;
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* A node's `[start, end)` character offsets into the whole source file.
|
|
1528
|
+
* Both engines always populate this -- ESLint forces it on regardless of
|
|
1529
|
+
* parser options, and Deno.lint exposes it the same way as every other
|
|
1530
|
+
* child property (see the `for...in` note on why plain property access
|
|
1531
|
+
* still works even though it's not an own enumerable property).
|
|
1532
|
+
*/
|
|
1533
|
+
function getRange(node) {
|
|
1534
|
+
return node.range;
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Builds the source text to scan for a delivery call: the reachable
|
|
1538
|
+
* statements of `root`, with every nested function literal either folded
|
|
1539
|
+
* in (its own reachable text spliced in place, wherever that function's
|
|
1540
|
+
* own declaration happens to live) or blanked out, depending on whether
|
|
1541
|
+
* `used` (from `computeUsedFunctions`) says it is actually invoked.
|
|
1542
|
+
*
|
|
1543
|
+
* Splices each function by its own range rather than by matching its
|
|
1544
|
+
* source text, and applies the splices from the end of the statement
|
|
1545
|
+
* backward. That keeps two functions with byte-identical bodies (e.g. two
|
|
1546
|
+
* object-literal methods that both merely call `ctx.sendActivity(...)`)
|
|
1547
|
+
* from colliding: a text-based replacement would find and blank out both
|
|
1548
|
+
* occurrences the first time either one is processed, since it matches by
|
|
1549
|
+
* content everywhere in the statement rather than by which node is
|
|
1550
|
+
* actually being replaced. Replacing from the end backward also means a
|
|
1551
|
+
* later replacement's length change never shifts the still-unprocessed
|
|
1552
|
+
* offsets of an earlier one.
|
|
1553
|
+
*/
|
|
1554
|
+
function collectDeliveryScanCode(sourceCode, root, used, visited) {
|
|
1555
|
+
if (visited.has(root)) return "";
|
|
1556
|
+
visited.add(root);
|
|
1557
|
+
const statements = [];
|
|
1558
|
+
collectReachableStatements(root, statements);
|
|
1559
|
+
return statements.map((statement) => {
|
|
1560
|
+
const text = sourceCode.getText(statement);
|
|
1561
|
+
const [statementStart] = getRange(statement);
|
|
1562
|
+
const nested = [];
|
|
1563
|
+
collectNestedFunctions(statement, nested);
|
|
1564
|
+
const byDescendingStart = [...nested].sort((a, b) => getRange(b)[0] - getRange(a)[0]);
|
|
1565
|
+
let result = text;
|
|
1566
|
+
for (const fn of byDescendingStart) {
|
|
1567
|
+
const [fnStart, fnEnd] = getRange(fn);
|
|
1568
|
+
const replacement = used.has(fn) ? collectDeliveryScanCode(sourceCode, fn.body, used, visited) : "";
|
|
1569
|
+
result = result.slice(0, fnStart - statementStart) + (replacement.length > 0 ? replacement : "()=>{}") + result.slice(fnEnd - statementStart);
|
|
1570
|
+
}
|
|
1571
|
+
return result;
|
|
1572
|
+
}).join("\n");
|
|
1573
|
+
}
|
|
1188
1574
|
const listenerCallsDeliveryMethod = (sourceCode, listener) => {
|
|
1189
|
-
const
|
|
1575
|
+
const used = computeUsedFunctions(listener.body);
|
|
1576
|
+
const code = stripCommentsAndStrings(collectDeliveryScanCode(sourceCode, listener.body, used, /* @__PURE__ */ new Set()));
|
|
1190
1577
|
const aliases = /* @__PURE__ */ new Set();
|
|
1191
1578
|
const contextParam = unwrapContextParam(listener.params[0]);
|
|
1192
1579
|
const contextName = contextParam?.type === "Identifier" ? contextParam.name : null;
|
|
@@ -1224,7 +1611,7 @@ function createRule(buildReport) {
|
|
|
1224
1611
|
if (!hasMemberExpressionCallee(node) || !hasIdentifierProperty(node) || !hasMethodName("on")(node) || node.arguments.length < 2) return;
|
|
1225
1612
|
if (!isChainedFromOutboxListeners(node.callee.object, federationTracker)) return;
|
|
1226
1613
|
const listener = node.arguments[1];
|
|
1227
|
-
const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ?
|
|
1614
|
+
const resolvedListener = isNode(listener) && isFunction(listener) ? listener : isNode(listener) ? resolveFunctionBinding(listener, bindings) : null;
|
|
1228
1615
|
if (resolvedListener == null) return;
|
|
1229
1616
|
if (listenerCallsDeliveryMethod(sourceCode, resolvedListener)) return;
|
|
1230
1617
|
context.report({
|
package/dist/oxlint.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-
|
|
1
|
+
const require_outbox_listener_delivery_required = require("./outbox-listener-delivery-required-glWw7_mh.cjs");
|
|
2
2
|
//#region src/oxlint.ts
|
|
3
3
|
const rules = {
|
|
4
4
|
[require_outbox_listener_delivery_required.RULE_IDS.actorIdMismatch]: require_outbox_listener_delivery_required.eslint$17,
|
package/dist/oxlint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as version, C as eslint$12, D as eslint$21, E as eslint$11, O as RULE_IDS, S as eslint$5, T as eslint$10, _ as eslint$1, a as eslint$18, b as eslint$3, c as eslint$17, d as eslint$6, f as eslint$7, g as eslint$15, h as eslint$14, i as eslint$23, k as name, l as eslint$20, m as eslint$9, n as eslint$25, o as eslint$19, p as eslint$8, r as eslint$26, s as eslint$16, t as eslint$24, u as eslint$22, v as eslint, w as eslint$13, x as eslint$4, y as eslint$2 } from "./outbox-listener-delivery-required-
|
|
1
|
+
import { A as version, C as eslint$12, D as eslint$21, E as eslint$11, O as RULE_IDS, S as eslint$5, T as eslint$10, _ as eslint$1, a as eslint$18, b as eslint$3, c as eslint$17, d as eslint$6, f as eslint$7, g as eslint$15, h as eslint$14, i as eslint$23, k as name, l as eslint$20, m as eslint$9, n as eslint$25, o as eslint$19, p as eslint$8, r as eslint$26, s as eslint$16, t as eslint$24, u as eslint$22, v as eslint, w as eslint$13, x as eslint$4, y as eslint$2 } from "./outbox-listener-delivery-required-DzoYEMe6.js";
|
|
2
2
|
//#region src/oxlint.ts
|
|
3
3
|
const rules = {
|
|
4
4
|
[RULE_IDS.actorIdMismatch]: eslint,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/lint",
|
|
3
|
-
"version": "2.4.0-dev.
|
|
3
|
+
"version": "2.4.0-dev.2010+498229f4",
|
|
4
4
|
"description": "Fedify linting rules and plugins",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Fedify",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
],
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"eslint": ">=9.0.0",
|
|
54
|
-
"@fedify/fedify": "^2.4.0-dev.
|
|
54
|
+
"@fedify/fedify": "^2.4.0-dev.2010+498229f4"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
57
57
|
"eslint": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"tsdown": "^0.22.0",
|
|
72
72
|
"typescript": "^6.0.0",
|
|
73
73
|
"@fedify/fixture": "2.0.0",
|
|
74
|
-
"@fedify/vocab": "2.4.0-dev.
|
|
74
|
+
"@fedify/vocab": "2.4.0-dev.2010+498229f4"
|
|
75
75
|
},
|
|
76
76
|
"scripts": {
|
|
77
77
|
"build:self": "tsdown",
|