eslint-plugin-unslop 0.2.0 → 0.2.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.
package/dist/index.cjs CHANGED
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(index_exports);
37
37
  // package.json
38
38
  var package_default = {
39
39
  name: "eslint-plugin-unslop",
40
- version: "0.2.0",
40
+ version: "0.2.1",
41
41
  description: "ESLint plugin with rules for reducing AI-generated code smells",
42
42
  repository: {
43
43
  type: "git",
@@ -1163,10 +1163,18 @@ function reportTopLevelOrdering(program, context) {
1163
1163
  const helpers = collectHelpers(body);
1164
1164
  const refs = collectReferences(context.sourceCode.scopeManager.globalScope);
1165
1165
  const cyclicNames = findCyclicHelperNames(body, helpers, refs);
1166
- const fixRange = buildTopLevelFixRange({ body, helpers, refs, cyclicNames, context });
1166
+ const eagerHelperNames = findEagerHelperNames(body, helpers, refs);
1167
+ const fixRange = buildTopLevelFixRange({
1168
+ body,
1169
+ helpers,
1170
+ refs,
1171
+ cyclicNames,
1172
+ eagerHelperNames,
1173
+ context
1174
+ });
1167
1175
  for (const helper of helpers) {
1168
1176
  if (cyclicNames.has(helper.name)) continue;
1169
- if (hasEagerReference(body, helper, refs)) continue;
1177
+ if (eagerHelperNames.has(helper.name)) continue;
1170
1178
  const consumer = findFirstConsumerEntry(body, helper, refs)?.statement;
1171
1179
  if (!consumer) continue;
1172
1180
  context.report({
@@ -1182,19 +1190,26 @@ function reportTopLevelOrdering(program, context) {
1182
1190
  }
1183
1191
  }
1184
1192
  function buildTopLevelFixRange(input) {
1185
- const { body, helpers, refs, cyclicNames, context } = input;
1193
+ const { body, helpers, refs, cyclicNames, eagerHelperNames, context } = input;
1186
1194
  if (body.length < 2) return void 0;
1187
- const edges = collectTopLevelEdges(body, helpers, refs, cyclicNames);
1195
+ const edges = collectTopLevelEdges({
1196
+ body,
1197
+ helpers,
1198
+ refs,
1199
+ cyclicNames,
1200
+ eagerHelperNames
1201
+ });
1188
1202
  if (edges.length === 0) return void 0;
1189
1203
  const order = stableTopologicalOrder(body.length, edges);
1190
1204
  if (!order || isIdentityOrder(order)) return void 0;
1191
1205
  const orderedBody = order.map((index) => body[index]);
1192
1206
  return createSafeReorderFix(context.sourceCode, body, orderedBody);
1193
1207
  }
1194
- function collectTopLevelEdges(body, helpers, refs, cyclicNames) {
1208
+ function collectTopLevelEdges(input) {
1209
+ const { body, helpers, refs, cyclicNames, eagerHelperNames } = input;
1195
1210
  const edges = [];
1196
1211
  for (const helper of helpers) {
1197
- if (cyclicNames.has(helper.name) || hasEagerReference(body, helper, refs)) continue;
1212
+ if (cyclicNames.has(helper.name) || eagerHelperNames.has(helper.name)) continue;
1198
1213
  const consumer = findFirstConsumerEntry(body, helper, refs);
1199
1214
  if (!consumer) continue;
1200
1215
  edges.push([consumer.index, helper.index]);
@@ -1322,32 +1337,78 @@ function collectReferences(scope) {
1322
1337
  }
1323
1338
  return refs;
1324
1339
  }
1325
- function hasEagerReference(body, helper, refs) {
1326
- for (let i = helper.index + 1; i < body.length; i += 1) {
1327
- const stmt = body[i];
1328
- if (stmt.type === "ExportNamedDeclaration" && !stmt.declaration) continue;
1329
- if (statementHasEagerUse(stmt, helper.name, refs)) return true;
1340
+ function findEagerHelperNames(body, helpers, refs) {
1341
+ const helperNames = new Set(helpers.map((helper) => helper.name));
1342
+ const deps = collectHelperRuntimeDependencies(body, helpers, refs, helperNames);
1343
+ const roots = collectEagerRootNames(body, refs, helperNames);
1344
+ return collectReachableNames(roots, deps);
1345
+ }
1346
+ function collectHelperRuntimeDependencies(body, helpers, refs, helperNames) {
1347
+ const deps = /* @__PURE__ */ new Map();
1348
+ for (const helper of helpers) {
1349
+ const statement = body[helper.index];
1350
+ if (!statement) continue;
1351
+ const names = collectRuntimeNamesInStatement(statement, refs, helperNames);
1352
+ names.delete(helper.name);
1353
+ deps.set(helper.name, names);
1330
1354
  }
1331
- return false;
1355
+ return deps;
1332
1356
  }
1333
- function statementHasEagerUse(statement, name, refs) {
1334
- const range = statement.range;
1335
- if (!range) return false;
1357
+ function collectEagerRootNames(body, refs, helperNames) {
1358
+ const roots = /* @__PURE__ */ new Set();
1336
1359
  for (const ref of refs) {
1337
- if (ref.identifier.name !== name || isTypeReference(ref)) continue;
1338
- if (isEagerRefInRange(ref, range)) return true;
1360
+ if (!isRuntimeHelperReadReference(ref, helperNames)) continue;
1361
+ if (!isEagerRefInTopLevelStatement(ref, body)) continue;
1362
+ roots.add(ref.identifier.name);
1339
1363
  }
1340
- return false;
1364
+ return roots;
1341
1365
  }
1342
- function isEagerRefInRange(ref, range) {
1343
- const idRange = ref.identifier.range;
1344
- if (!idRange) return false;
1345
- const inside = idRange[0] >= range[0] && idRange[1] <= range[1];
1346
- return inside && (ref.from.type === "module" || ref.from.type === "global");
1366
+ function isEagerRefInTopLevelStatement(ref, body) {
1367
+ if (ref.from.type !== "module" && ref.from.type !== "global") return false;
1368
+ const statement = findTopLevelStatementForRef(body, ref);
1369
+ if (!statement) return false;
1370
+ return statement.type !== "ExportNamedDeclaration" || !!statement.declaration;
1371
+ }
1372
+ function findTopLevelStatementForRef(body, ref) {
1373
+ return body.find((statement) => isReferenceInsideStatement(ref, statement));
1374
+ }
1375
+ function collectRuntimeNamesInStatement(statement, refs, helperNames) {
1376
+ const names = /* @__PURE__ */ new Set();
1377
+ if (!statement.range) return names;
1378
+ for (const ref of refs) {
1379
+ if (!isRuntimeHelperReadReference(ref, helperNames)) continue;
1380
+ if (!isReferenceInsideStatement(ref, statement)) continue;
1381
+ names.add(ref.identifier.name);
1382
+ }
1383
+ return names;
1384
+ }
1385
+ function isReferenceInsideStatement(ref, statement) {
1386
+ const range = ref.identifier.range;
1387
+ if (!range || !statement.range) return false;
1388
+ return range[0] >= statement.range[0] && range[1] <= statement.range[1];
1389
+ }
1390
+ function collectReachableNames(roots, deps) {
1391
+ const reachable = /* @__PURE__ */ new Set();
1392
+ const queue = [...roots];
1393
+ while (queue.length > 0) {
1394
+ const current = queue.shift();
1395
+ if (!current || reachable.has(current)) continue;
1396
+ reachable.add(current);
1397
+ for (const dep of deps.get(current) ?? []) queue.push(dep);
1398
+ }
1399
+ return reachable;
1400
+ }
1401
+ function isRuntimeHelperReadReference(ref, helperNames) {
1402
+ if (isTypeReference(ref)) return false;
1403
+ if (!isReadReference(ref)) return false;
1404
+ return helperNames.has(ref.identifier.name);
1347
1405
  }
1348
1406
  function isTypeReference(ref) {
1349
1407
  return "isTypeReference" in ref && ref.isTypeReference === true;
1350
1408
  }
1409
+ function isReadReference(ref) {
1410
+ return typeof ref.isRead === "function" ? ref.isRead() : true;
1411
+ }
1351
1412
  function getSymbolName(statement) {
1352
1413
  if (statement.type === "ExportDefaultDeclaration") return "default export";
1353
1414
  if (statement.type === "ExportNamedDeclaration") return getNamedExportName(statement);