eslint-plugin-unslop 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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.6.1",
40
+ version: "0.7.0",
41
41
  description: "ESLint plugin with rules for reducing AI-generated code smells",
42
42
  repository: {
43
43
  type: "git",
@@ -301,7 +301,7 @@ var WRAPPER_UNSAFE = {
301
301
 
302
302
  // src/rules/no-false-sharing/index.ts
303
303
  var import_node_path4 = __toESM(require("path"), 1);
304
- var import_typescript3 = __toESM(require("typescript"), 1);
304
+ var import_typescript4 = __toESM(require("typescript"), 1);
305
305
 
306
306
  // src/utils/architecture-policy.ts
307
307
  var import_node_path3 = __toESM(require("path"), 1);
@@ -764,6 +764,61 @@ function hasStringName(value) {
764
764
  return typeof id.name === "string";
765
765
  }
766
766
 
767
+ // src/utils/project-symbol-usage.ts
768
+ var import_typescript3 = __toESM(require("typescript"), 1);
769
+ function findTsSourceFile(program, filename) {
770
+ const normalized = normalizeResolvedPath2(filename);
771
+ return program.getSourceFiles().find((sf) => normalizeResolvedPath2(sf.fileName) === normalized);
772
+ }
773
+ function countProjectWideUses(targetSymbol, checker, program) {
774
+ let total = 0;
775
+ for (const sf of program.getSourceFiles()) {
776
+ if (sf.isDeclarationFile) continue;
777
+ total += countUsesInFile(sf, targetSymbol, checker);
778
+ }
779
+ return total;
780
+ }
781
+ function countUsesInFile(sourceFile, targetSymbol, checker) {
782
+ let count = 0;
783
+ function walk(node) {
784
+ if (import_typescript3.default.isIdentifier(node) && isCountableUse(node, targetSymbol, checker)) {
785
+ count++;
786
+ }
787
+ import_typescript3.default.forEachChild(node, walk);
788
+ }
789
+ walk(sourceFile);
790
+ return count;
791
+ }
792
+ function isCountableUse(node, targetSymbol, checker) {
793
+ if (!isRealUsagePosition(node)) return false;
794
+ const sym = checker.getSymbolAtLocation(node);
795
+ if (sym === void 0) return false;
796
+ return resolveCanonicalSymbol(sym, checker) === targetSymbol;
797
+ }
798
+ function isRealUsagePosition(node) {
799
+ const parent = node.parent;
800
+ if (isDeclarationBinding(parent, node)) return false;
801
+ if (isImportPosition(parent, node)) return false;
802
+ if (import_typescript3.default.isExportSpecifier(parent)) return false;
803
+ if (import_typescript3.default.isExportAssignment(parent)) return false;
804
+ return true;
805
+ }
806
+ function isDeclarationBinding(parent, node) {
807
+ return isNamedDeclParent(parent) && parent.name === node;
808
+ }
809
+ function isNamedDeclParent(parent) {
810
+ return import_typescript3.default.isVariableDeclaration(parent) || import_typescript3.default.isTypeAliasDeclaration(parent) || import_typescript3.default.isInterfaceDeclaration(parent) || import_typescript3.default.isFunctionDeclaration(parent) || import_typescript3.default.isClassDeclaration(parent);
811
+ }
812
+ function isImportPosition(parent, node) {
813
+ if (import_typescript3.default.isImportSpecifier(parent)) return true;
814
+ if (import_typescript3.default.isImportClause(parent) && parent.name === node) return true;
815
+ return import_typescript3.default.isNamespaceImport(parent) && parent.name === node;
816
+ }
817
+ function resolveCanonicalSymbol(symbol, checker) {
818
+ if ((symbol.flags & import_typescript3.default.SymbolFlags.Alias) === 0) return symbol;
819
+ return checker.getAliasedSymbol(symbol);
820
+ }
821
+
767
822
  // src/rules/no-false-sharing/index.ts
768
823
  var no_false_sharing_default = {
769
824
  meta: {
@@ -954,11 +1009,11 @@ function isProjectSourceFile(filePath, options) {
954
1009
  function collectImportedSymbolUsages(sourceFile, projectContext) {
955
1010
  const usages = [];
956
1011
  for (const statement of sourceFile.statements) {
957
- if (import_typescript3.default.isImportDeclaration(statement)) {
1012
+ if (import_typescript4.default.isImportDeclaration(statement)) {
958
1013
  addImportDeclarationUsages(statement, sourceFile, projectContext, usages);
959
1014
  continue;
960
1015
  }
961
- if (import_typescript3.default.isExportDeclaration(statement)) {
1016
+ if (import_typescript4.default.isExportDeclaration(statement)) {
962
1017
  addExportDeclarationUsages(statement, sourceFile, projectContext, usages);
963
1018
  }
964
1019
  }
@@ -972,7 +1027,7 @@ function addImportDeclarationUsages(statement, sourceFile, projectContext, usage
972
1027
  addIdentifierUsage(statement.importClause.name, targetFile, projectContext, usages);
973
1028
  }
974
1029
  const namedBindings = statement.importClause.namedBindings;
975
- if (namedBindings === void 0 || !import_typescript3.default.isNamedImports(namedBindings)) return;
1030
+ if (namedBindings === void 0 || !import_typescript4.default.isNamedImports(namedBindings)) return;
976
1031
  for (const element of namedBindings.elements) {
977
1032
  addIdentifierUsage(element.name, targetFile, projectContext, usages);
978
1033
  }
@@ -980,15 +1035,15 @@ function addImportDeclarationUsages(statement, sourceFile, projectContext, usage
980
1035
  function addExportDeclarationUsages(statement, sourceFile, projectContext, usages) {
981
1036
  const targetFile = getResolvedTargetFile(statement.moduleSpecifier, sourceFile, projectContext);
982
1037
  if (targetFile === void 0) return;
983
- if (statement.exportClause === void 0 || !import_typescript3.default.isNamedExports(statement.exportClause)) return;
1038
+ if (statement.exportClause === void 0 || !import_typescript4.default.isNamedExports(statement.exportClause)) return;
984
1039
  for (const element of statement.exportClause.elements) {
985
1040
  const reference = element.propertyName ?? element.name;
986
- if (!import_typescript3.default.isIdentifier(reference)) continue;
1041
+ if (!import_typescript4.default.isIdentifier(reference)) continue;
987
1042
  addIdentifierUsage(reference, targetFile, projectContext, usages);
988
1043
  }
989
1044
  }
990
1045
  function getResolvedTargetFile(moduleSpecifier, sourceFile, projectContext) {
991
- if (moduleSpecifier === void 0 || !import_typescript3.default.isStringLiteral(moduleSpecifier)) return void 0;
1046
+ if (moduleSpecifier === void 0 || !import_typescript4.default.isStringLiteral(moduleSpecifier)) return void 0;
992
1047
  return resolveImportTarget(sourceFile.fileName, projectContext, moduleSpecifier.text);
993
1048
  }
994
1049
  function addIdentifierUsage(identifier, targetFile, projectContext, usages) {
@@ -1000,7 +1055,7 @@ function addIdentifierUsage(identifier, targetFile, projectContext, usages) {
1000
1055
  usages.push({ canonicalKey, targetFile });
1001
1056
  }
1002
1057
  function getCanonicalSymbol(checker, symbol) {
1003
- if ((symbol.flags & import_typescript3.default.SymbolFlags.Alias) === 0) return symbol;
1058
+ if ((symbol.flags & import_typescript4.default.SymbolFlags.Alias) === 0) return symbol;
1004
1059
  return checker.getAliasedSymbol(symbol);
1005
1060
  }
1006
1061
  function getCanonicalSymbolKey(symbol) {
@@ -1285,17 +1340,23 @@ function visitValue(val, visit) {
1285
1340
 
1286
1341
  // src/rules/read-friendly-order/kahns-sort.ts
1287
1342
  function kahnsTopologicalSort(items, priority) {
1288
- const byName = new Map(items.filter((e) => e.name).map((e) => [e.name, e]));
1289
- const inDeg = buildInDegrees(items, byName);
1343
+ const byName = /* @__PURE__ */ new Map();
1344
+ for (const e of items) {
1345
+ if (e.name) byName.set(e.name, e);
1346
+ }
1347
+ const { inDeg, eagerDependents } = buildInDegrees(items, byName);
1290
1348
  const queue = items.filter((e) => !e.name || inDeg.get(e.name) === 0);
1291
1349
  const result = [];
1292
- const state = { placed: /* @__PURE__ */ new Set(), inDeg, byName };
1350
+ const state = { placed: /* @__PURE__ */ new Set(), inDeg, byName, eagerDependents };
1293
1351
  drainQueue(queue, result, state, priority);
1294
1352
  appendRemaining(items, result, state.placed);
1295
1353
  return result;
1296
1354
  }
1297
1355
  function findCyclicNames(items) {
1298
- const byName = new Map(items.filter((e) => e.name).map((e) => [e.name, e]));
1356
+ const byName = /* @__PURE__ */ new Map();
1357
+ for (const e of items) {
1358
+ if (e.name) byName.set(e.name, e);
1359
+ }
1299
1360
  const inCycle = /* @__PURE__ */ new Set();
1300
1361
  for (const [name] of byName) {
1301
1362
  if (reachesSelf(name, name, byName, /* @__PURE__ */ new Set())) {
@@ -1306,33 +1367,67 @@ function findCyclicNames(items) {
1306
1367
  }
1307
1368
  function buildInDegrees(items, byName) {
1308
1369
  const inDeg = /* @__PURE__ */ new Map();
1309
- for (const [name] of byName) inDeg.set(name, 0);
1370
+ const eagerDependents = /* @__PURE__ */ new Map();
1371
+ for (const [name] of byName) {
1372
+ inDeg.set(name, 0);
1373
+ eagerDependents.set(name, []);
1374
+ }
1310
1375
  for (const item of items) {
1311
- for (const d of item.deps) {
1312
- if (inDeg.has(d)) inDeg.set(d, inDeg.get(d) + 1);
1376
+ processItemDeps(item, inDeg, eagerDependents);
1377
+ }
1378
+ return { inDeg, eagerDependents };
1379
+ }
1380
+ function processItemDeps(item, inDeg, eagerDependents) {
1381
+ for (const d of item.deps) {
1382
+ if (!inDeg.has(d)) continue;
1383
+ if (item.eager && item.name && inDeg.has(item.name)) {
1384
+ addEagerDependent(item.name, d, inDeg, eagerDependents);
1385
+ } else {
1386
+ inDeg.set(d, (inDeg.get(d) ?? 0) + 1);
1313
1387
  }
1314
1388
  }
1315
- return inDeg;
1389
+ }
1390
+ function addEagerDependent(consumerName, dep, inDeg, eagerDependents) {
1391
+ inDeg.set(consumerName, (inDeg.get(consumerName) ?? 0) + 1);
1392
+ const list = eagerDependents.get(dep) ?? [];
1393
+ list.push(consumerName);
1394
+ eagerDependents.set(dep, list);
1316
1395
  }
1317
1396
  function drainQueue(queue, result, state, priority) {
1318
1397
  while (queue.length > 0) {
1319
1398
  queue.sort((a, b) => (priority ? priority(a) - priority(b) : 0) || a.idx - b.idx);
1320
1399
  const item = queue.shift();
1400
+ if (item === void 0) break;
1321
1401
  result.push(item);
1322
1402
  if (item.name) state.placed.add(item.name);
1323
1403
  decrementDeps(item, queue, state);
1324
1404
  }
1325
1405
  }
1326
1406
  function decrementDeps(item, queue, state) {
1407
+ decrementNonEager(item, queue, state);
1408
+ decrementEager(item, queue, state);
1409
+ }
1410
+ function decrementNonEager(item, queue, state) {
1327
1411
  for (const d of item.deps) {
1328
1412
  if (state.placed.has(d) || !state.inDeg.has(d)) continue;
1329
- state.inDeg.set(d, state.inDeg.get(d) - 1);
1330
- if (state.inDeg.get(d) === 0) {
1331
- const dm = state.byName.get(d);
1332
- if (dm && !state.placed.has(d)) queue.push(dm);
1333
- }
1413
+ const current = state.inDeg.get(d) ?? 0;
1414
+ state.inDeg.set(d, current - 1);
1415
+ if (current - 1 === 0) enqueueByName(d, queue, state);
1334
1416
  }
1335
1417
  }
1418
+ function decrementEager(item, queue, state) {
1419
+ if (!item.name) return;
1420
+ for (const consumerName of state.eagerDependents.get(item.name) ?? []) {
1421
+ if (state.placed.has(consumerName) || !state.inDeg.has(consumerName)) continue;
1422
+ const current = state.inDeg.get(consumerName) ?? 0;
1423
+ state.inDeg.set(consumerName, current - 1);
1424
+ if (current - 1 === 0) enqueueByName(consumerName, queue, state);
1425
+ }
1426
+ }
1427
+ function enqueueByName(name, queue, state) {
1428
+ const entry = state.byName.get(name);
1429
+ if (entry && !state.placed.has(name)) queue.push(entry);
1430
+ }
1336
1431
  function appendRemaining(items, result, placed) {
1337
1432
  for (const item of items) {
1338
1433
  if (item.name && !placed.has(item.name)) result.push(item);
@@ -2115,7 +2210,7 @@ function isEntrypointFile(filePath, entrypoints) {
2115
2210
  }
2116
2211
 
2117
2212
  // src/rules/no-single-use-constants/index.ts
2118
- var import_typescript4 = __toESM(require("typescript"), 1);
2213
+ var import_typescript5 = __toESM(require("typescript"), 1);
2119
2214
  var no_single_use_constants_default = {
2120
2215
  meta: {
2121
2216
  type: "suggestion",
@@ -2198,11 +2293,7 @@ function extractConstDeclarators(stmt) {
2198
2293
  function isExcludedInitializer(declarator) {
2199
2294
  const init = declarator.init;
2200
2295
  if (init === null || init === void 0) return true;
2201
- return init.type === "ObjectExpression" || init.type === "NewExpression" || init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression" || init.type === "ClassExpression" || hasExplicitTypeArguments(init);
2202
- }
2203
- function hasExplicitTypeArguments(node) {
2204
- if (node.type !== "CallExpression") return false;
2205
- return node.typeArguments !== void 0 || node.typeParameters !== void 0;
2296
+ return init.type === "ObjectExpression" || init.type === "NewExpression" || init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression" || init.type === "ClassExpression" || init.type === "CallExpression";
2206
2297
  }
2207
2298
  function collectExportedNames(program, names) {
2208
2299
  for (const stmt of program.body) {
@@ -2242,65 +2333,124 @@ function countExportedUses(name, filename, tsContext) {
2242
2333
  if (targetSymbol === void 0) return void 0;
2243
2334
  return countProjectWideUses(targetSymbol, tsContext.checker, tsContext.program);
2244
2335
  }
2245
- function findTsSourceFile(program, filename) {
2246
- const normalized = normalizeResolvedPath2(filename);
2247
- return program.getSourceFiles().find((sf) => normalizeResolvedPath2(sf.fileName) === normalized);
2248
- }
2249
2336
  function findDeclarationSymbol(sourceFile, name, checker) {
2250
2337
  for (const stmt of sourceFile.statements) {
2251
- if (!import_typescript4.default.isVariableStatement(stmt)) continue;
2252
- if ((stmt.declarationList.flags & import_typescript4.default.NodeFlags.Const) === 0) continue;
2338
+ if (!import_typescript5.default.isVariableStatement(stmt)) continue;
2339
+ if ((stmt.declarationList.flags & import_typescript5.default.NodeFlags.Const) === 0) continue;
2253
2340
  for (const decl of stmt.declarationList.declarations) {
2254
- if (!import_typescript4.default.isIdentifier(decl.name) || decl.name.text !== name) continue;
2341
+ if (!import_typescript5.default.isIdentifier(decl.name) || decl.name.text !== name) continue;
2255
2342
  return checker.getSymbolAtLocation(decl.name);
2256
2343
  }
2257
2344
  }
2258
2345
  return void 0;
2259
2346
  }
2260
- function countProjectWideUses(targetSymbol, checker, program) {
2261
- let total = 0;
2262
- for (const sf of program.getSourceFiles()) {
2263
- if (sf.isDeclarationFile) continue;
2264
- total += countUsesInFile(sf, targetSymbol, checker);
2265
- }
2266
- return total;
2267
- }
2268
- function countUsesInFile(sourceFile, targetSymbol, checker) {
2269
- let count = 0;
2270
- function walk(node) {
2271
- if (import_typescript4.default.isIdentifier(node) && isCountableUse(node, targetSymbol, checker)) {
2272
- count++;
2347
+
2348
+ // src/rules/no-unused-types/index.ts
2349
+ var import_typescript6 = __toESM(require("typescript"), 1);
2350
+ var no_unused_types_default = {
2351
+ meta: {
2352
+ type: "suggestion",
2353
+ docs: {
2354
+ description: "Disallow exported type aliases and interfaces with zero project-wide uses",
2355
+ recommended: false
2356
+ },
2357
+ schema: [],
2358
+ messages: {
2359
+ configurationError: "Configuration error: {{details}}",
2360
+ zeroUses: 'type "{{name}}" has no real uses across the project; remove it'
2273
2361
  }
2274
- import_typescript4.default.forEachChild(node, walk);
2362
+ },
2363
+ create(context) {
2364
+ const filename = context.filename;
2365
+ return {
2366
+ "Program:exit"(node) {
2367
+ const exportedTypeNames = collectExportedTypeNames(node);
2368
+ if (filename.length === 0 || exportedTypeNames.size === 0) return;
2369
+ const projectContext = getRequiredTypeScriptProjectContext(filename);
2370
+ if (projectContext.kind === "context-error") {
2371
+ context.report({
2372
+ node,
2373
+ messageId: "configurationError",
2374
+ data: {
2375
+ details: formatProjectContextError(projectContext.error)
2376
+ }
2377
+ });
2378
+ return;
2379
+ }
2380
+ const tsContext = projectContext.projectContext;
2381
+ for (const name of exportedTypeNames) {
2382
+ checkType(name, filename, tsContext, context);
2383
+ }
2384
+ }
2385
+ };
2275
2386
  }
2276
- walk(sourceFile);
2277
- return count;
2387
+ };
2388
+ function collectExportedTypeNames(program) {
2389
+ const names = /* @__PURE__ */ new Set();
2390
+ for (const stmt of program.body) {
2391
+ const name = getExportedTypeName(stmt);
2392
+ if (name !== void 0) names.add(name);
2393
+ }
2394
+ return names;
2278
2395
  }
2279
- function isCountableUse(node, targetSymbol, checker) {
2280
- if (!isRealUsagePosition(node)) return false;
2281
- const sym = checker.getSymbolAtLocation(node);
2282
- if (sym === void 0) return false;
2283
- return resolveCanonicalSymbol(sym, checker) === targetSymbol;
2396
+ function getExportedTypeName(stmt) {
2397
+ if (stmt.type !== "ExportNamedDeclaration") return void 0;
2398
+ const decl = stmt.declaration;
2399
+ if (decl === null || decl === void 0) return void 0;
2400
+ const declType = strProp2(decl, "type");
2401
+ if (declType !== "TSTypeAliasDeclaration" && declType !== "TSInterfaceDeclaration")
2402
+ return void 0;
2403
+ const idNode = prop2(decl, "id");
2404
+ return strProp2(idNode, "name");
2284
2405
  }
2285
- function isRealUsagePosition(node) {
2286
- const parent = node.parent;
2287
- if (isDeclarationBinding(parent, node)) return false;
2288
- if (isImportPosition(parent, node)) return false;
2289
- if (import_typescript4.default.isExportSpecifier(parent)) return false;
2290
- if (import_typescript4.default.isExportAssignment(parent)) return false;
2291
- return true;
2406
+ function checkType(name, filename, tsContext, context) {
2407
+ const sourceFile = findTsSourceFile(tsContext.program, filename);
2408
+ if (sourceFile === void 0) return;
2409
+ const targetSymbol = findTypeDeclarationSymbol(sourceFile, name, tsContext.checker);
2410
+ if (targetSymbol === void 0) return;
2411
+ const count = countProjectWideUses(targetSymbol, tsContext.checker, tsContext.program);
2412
+ if (count > 0) return;
2413
+ const decl = targetSymbol.declarations?.[0];
2414
+ if (decl === void 0) return;
2415
+ context.report({
2416
+ node: context.sourceCode.ast,
2417
+ loc: {
2418
+ start: {
2419
+ line: import_typescript6.default.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1,
2420
+ column: import_typescript6.default.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).character
2421
+ },
2422
+ end: {
2423
+ line: import_typescript6.default.getLineAndCharacterOfPosition(sourceFile, decl.getEnd()).line + 1,
2424
+ column: import_typescript6.default.getLineAndCharacterOfPosition(sourceFile, decl.getEnd()).character
2425
+ }
2426
+ },
2427
+ messageId: "zeroUses",
2428
+ data: { name }
2429
+ });
2292
2430
  }
2293
- function isDeclarationBinding(parent, node) {
2294
- return import_typescript4.default.isVariableDeclaration(parent) && parent.name === node;
2431
+ function findTypeDeclarationSymbol(sourceFile, name, checker) {
2432
+ for (const stmt of sourceFile.statements) {
2433
+ const decl = getTypedDeclaration(stmt);
2434
+ if (decl === void 0) continue;
2435
+ if (!import_typescript6.default.isIdentifier(decl.name) || decl.name.text !== name) continue;
2436
+ const sym = checker.getSymbolAtLocation(decl.name);
2437
+ if (sym !== void 0) return sym;
2438
+ }
2439
+ return void 0;
2295
2440
  }
2296
- function isImportPosition(parent, node) {
2297
- if (import_typescript4.default.isImportSpecifier(parent)) return true;
2298
- if (import_typescript4.default.isImportClause(parent) && parent.name === node) return true;
2299
- return import_typescript4.default.isNamespaceImport(parent) && parent.name === node;
2441
+ function getTypedDeclaration(stmt) {
2442
+ if (import_typescript6.default.isTypeAliasDeclaration(stmt) || import_typescript6.default.isInterfaceDeclaration(stmt)) {
2443
+ return stmt;
2444
+ }
2445
+ return void 0;
2300
2446
  }
2301
- function resolveCanonicalSymbol(symbol, checker) {
2302
- if ((symbol.flags & import_typescript4.default.SymbolFlags.Alias) === 0) return symbol;
2303
- return checker.getAliasedSymbol(symbol);
2447
+ function strProp2(obj, key) {
2448
+ const v = prop2(obj, key);
2449
+ return typeof v === "string" ? v : void 0;
2450
+ }
2451
+ function prop2(obj, key) {
2452
+ if (typeof obj !== "object" || obj === null) return void 0;
2453
+ return Reflect.get(obj, key);
2304
2454
  }
2305
2455
 
2306
2456
  // src/rules/index.ts
@@ -2312,7 +2462,8 @@ var rules_default = {
2312
2462
  "import-control": import_control_default,
2313
2463
  "no-whitebox-testing": no_whitebox_testing_default,
2314
2464
  "export-control": export_control_default,
2315
- "no-single-use-constants": no_single_use_constants_default
2465
+ "no-single-use-constants": no_single_use_constants_default,
2466
+ "no-unused-types": no_unused_types_default
2316
2467
  };
2317
2468
 
2318
2469
  // src/index.ts
@@ -2343,6 +2494,7 @@ var full = {
2343
2494
  "unslop/export-control": "error",
2344
2495
  "unslop/no-false-sharing": "error",
2345
2496
  "unslop/no-single-use-constants": "error",
2497
+ "unslop/no-unused-types": "error",
2346
2498
  "unslop/read-friendly-order": "error"
2347
2499
  }
2348
2500
  };