eslint-plugin-unslop 0.6.0 → 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/README.md CHANGED
@@ -85,13 +85,16 @@ Each value is a policy object:
85
85
 
86
86
  ```ts
87
87
  {
88
- imports?: string[] // exact module, direct child via /*, self-or-child via /+, or '*' for all
89
- exports?: string[] // regex patterns symbols exported from entrypoints must match
90
- entrypoints?: string[] // public files allowed for external and test imports
91
- shared?: boolean // marks module as shared; enables no-false-sharing
88
+ imports?: string[] // exact module, direct child via /*, self-or-child via /+, or '*' for all
89
+ typeImports?: string[] // same patterns as imports, but only for type-only imports
90
+ exports?: string[] // regex patterns symbols exported from entrypoints must match
91
+ entrypoints?: string[] // public files allowed for external and test imports
92
+ shared?: boolean // marks module as shared; enables no-false-sharing
92
93
  }
93
94
  ```
94
95
 
96
+ `typeImports` defaults to `[]` when omitted. Type-only imports are also allowed when the target matches `imports`, so `typeImports` is only needed for modules you want to allow type access to without allowing value imports.
97
+
95
98
  Architecture keys and import allowlists use different matching rules:
96
99
 
97
100
  - keys assign ownership to subtrees
@@ -99,7 +102,7 @@ Architecture keys and import allowlists use different matching rules:
99
102
  - `imports: ['models/*']` allows only direct children like `models/user`
100
103
  - `imports: ['models/+']` allows `models` and direct children like `models/user`
101
104
 
102
- When multiple keys cover the same canonical module path, the winner is chosen by nearest owner first, then exact named path over wildcard path at the same depth, then longer selector path, then declaration order. Unmatched canonical module paths become anonymous modules with empty `imports`, empty `exports`, `shared: false`, and default `entrypoints: ['index.ts']`.
105
+ When multiple keys cover the same canonical module path, the winner is chosen by nearest owner first, then exact named path over wildcard path at the same depth, then longer selector path, then declaration order. Unmatched canonical module paths become anonymous modules with empty `imports`, empty `typeImports`, empty `exports`, `shared: false`, and default `entrypoints: ['index.ts']`.
103
106
 
104
107
  All architecture rules take no options. Policy comes entirely from this shared settings block.
105
108
 
@@ -112,6 +115,7 @@ Customs control for your modules: you declare which modules are allowed to impor
112
115
  Deny-by-default for cross-module imports, so forgetting to declare a dependency is a loud error rather than a silent free-for-all. It also enforces:
113
116
 
114
117
  - cross-module imports must arrive through the public gate (configured entrypoints)
118
+ - type-only imports can be separately allowed via `typeImports` (value imports from those modules remain forbidden)
115
119
  - local cross-module namespace imports are forbidden (`import * as X from '<local-module>'`)
116
120
  - same-module relative imports can only go one level deeper - no tunnelling into internals
117
121
  - files that don't match any declared module become anonymous modules and are denied by default
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.0",
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);
@@ -632,11 +632,13 @@ function getUnsupportedArchitectureKeyDetails(matcher) {
632
632
  function parseModulePolicy(rawPolicy) {
633
633
  if (!isRecord(rawPolicy)) return void 0;
634
634
  const imports = readStringList(rawPolicy.imports);
635
+ const typeImports = readStringList(rawPolicy.typeImports);
635
636
  const exports2 = readStringList(rawPolicy.exports);
636
637
  const entrypoints = readStringList(rawPolicy.entrypoints);
637
638
  const shared = rawPolicy.shared === true;
638
639
  return {
639
640
  imports,
641
+ typeImports,
640
642
  exports: exports2,
641
643
  entrypoints: entrypoints.length > 0 ? entrypoints : ["index.ts"],
642
644
  shared
@@ -704,7 +706,13 @@ function makeAnonymousModule(canonicalPath) {
704
706
  canonicalPath,
705
707
  ownerKey: canonicalPath,
706
708
  ownerPath: canonicalPath,
707
- policy: { imports: [], exports: [], entrypoints: ["index.ts"], shared: false },
709
+ policy: {
710
+ imports: [],
711
+ typeImports: [],
712
+ exports: [],
713
+ entrypoints: ["index.ts"],
714
+ shared: false
715
+ },
708
716
  order: 0,
709
717
  anonymous: true,
710
718
  ownerDepth: depth,
@@ -756,6 +764,61 @@ function hasStringName(value) {
756
764
  return typeof id.name === "string";
757
765
  }
758
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
+
759
822
  // src/rules/no-false-sharing/index.ts
760
823
  var no_false_sharing_default = {
761
824
  meta: {
@@ -946,11 +1009,11 @@ function isProjectSourceFile(filePath, options) {
946
1009
  function collectImportedSymbolUsages(sourceFile, projectContext) {
947
1010
  const usages = [];
948
1011
  for (const statement of sourceFile.statements) {
949
- if (import_typescript3.default.isImportDeclaration(statement)) {
1012
+ if (import_typescript4.default.isImportDeclaration(statement)) {
950
1013
  addImportDeclarationUsages(statement, sourceFile, projectContext, usages);
951
1014
  continue;
952
1015
  }
953
- if (import_typescript3.default.isExportDeclaration(statement)) {
1016
+ if (import_typescript4.default.isExportDeclaration(statement)) {
954
1017
  addExportDeclarationUsages(statement, sourceFile, projectContext, usages);
955
1018
  }
956
1019
  }
@@ -964,7 +1027,7 @@ function addImportDeclarationUsages(statement, sourceFile, projectContext, usage
964
1027
  addIdentifierUsage(statement.importClause.name, targetFile, projectContext, usages);
965
1028
  }
966
1029
  const namedBindings = statement.importClause.namedBindings;
967
- if (namedBindings === void 0 || !import_typescript3.default.isNamedImports(namedBindings)) return;
1030
+ if (namedBindings === void 0 || !import_typescript4.default.isNamedImports(namedBindings)) return;
968
1031
  for (const element of namedBindings.elements) {
969
1032
  addIdentifierUsage(element.name, targetFile, projectContext, usages);
970
1033
  }
@@ -972,15 +1035,15 @@ function addImportDeclarationUsages(statement, sourceFile, projectContext, usage
972
1035
  function addExportDeclarationUsages(statement, sourceFile, projectContext, usages) {
973
1036
  const targetFile = getResolvedTargetFile(statement.moduleSpecifier, sourceFile, projectContext);
974
1037
  if (targetFile === void 0) return;
975
- 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;
976
1039
  for (const element of statement.exportClause.elements) {
977
1040
  const reference = element.propertyName ?? element.name;
978
- if (!import_typescript3.default.isIdentifier(reference)) continue;
1041
+ if (!import_typescript4.default.isIdentifier(reference)) continue;
979
1042
  addIdentifierUsage(reference, targetFile, projectContext, usages);
980
1043
  }
981
1044
  }
982
1045
  function getResolvedTargetFile(moduleSpecifier, sourceFile, projectContext) {
983
- 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;
984
1047
  return resolveImportTarget(sourceFile.fileName, projectContext, moduleSpecifier.text);
985
1048
  }
986
1049
  function addIdentifierUsage(identifier, targetFile, projectContext, usages) {
@@ -992,7 +1055,7 @@ function addIdentifierUsage(identifier, targetFile, projectContext, usages) {
992
1055
  usages.push({ canonicalKey, targetFile });
993
1056
  }
994
1057
  function getCanonicalSymbol(checker, symbol) {
995
- if ((symbol.flags & import_typescript3.default.SymbolFlags.Alias) === 0) return symbol;
1058
+ if ((symbol.flags & import_typescript4.default.SymbolFlags.Alias) === 0) return symbol;
996
1059
  return checker.getAliasedSymbol(symbol);
997
1060
  }
998
1061
  function getCanonicalSymbolKey(symbol) {
@@ -1277,17 +1340,23 @@ function visitValue(val, visit) {
1277
1340
 
1278
1341
  // src/rules/read-friendly-order/kahns-sort.ts
1279
1342
  function kahnsTopologicalSort(items, priority) {
1280
- const byName = new Map(items.filter((e) => e.name).map((e) => [e.name, e]));
1281
- 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);
1282
1348
  const queue = items.filter((e) => !e.name || inDeg.get(e.name) === 0);
1283
1349
  const result = [];
1284
- const state = { placed: /* @__PURE__ */ new Set(), inDeg, byName };
1350
+ const state = { placed: /* @__PURE__ */ new Set(), inDeg, byName, eagerDependents };
1285
1351
  drainQueue(queue, result, state, priority);
1286
1352
  appendRemaining(items, result, state.placed);
1287
1353
  return result;
1288
1354
  }
1289
1355
  function findCyclicNames(items) {
1290
- 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
+ }
1291
1360
  const inCycle = /* @__PURE__ */ new Set();
1292
1361
  for (const [name] of byName) {
1293
1362
  if (reachesSelf(name, name, byName, /* @__PURE__ */ new Set())) {
@@ -1298,33 +1367,67 @@ function findCyclicNames(items) {
1298
1367
  }
1299
1368
  function buildInDegrees(items, byName) {
1300
1369
  const inDeg = /* @__PURE__ */ new Map();
1301
- 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
+ }
1302
1375
  for (const item of items) {
1303
- for (const d of item.deps) {
1304
- 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);
1305
1387
  }
1306
1388
  }
1307
- 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);
1308
1395
  }
1309
1396
  function drainQueue(queue, result, state, priority) {
1310
1397
  while (queue.length > 0) {
1311
1398
  queue.sort((a, b) => (priority ? priority(a) - priority(b) : 0) || a.idx - b.idx);
1312
1399
  const item = queue.shift();
1400
+ if (item === void 0) break;
1313
1401
  result.push(item);
1314
1402
  if (item.name) state.placed.add(item.name);
1315
1403
  decrementDeps(item, queue, state);
1316
1404
  }
1317
1405
  }
1318
1406
  function decrementDeps(item, queue, state) {
1407
+ decrementNonEager(item, queue, state);
1408
+ decrementEager(item, queue, state);
1409
+ }
1410
+ function decrementNonEager(item, queue, state) {
1319
1411
  for (const d of item.deps) {
1320
1412
  if (state.placed.has(d) || !state.inDeg.has(d)) continue;
1321
- state.inDeg.set(d, state.inDeg.get(d) - 1);
1322
- if (state.inDeg.get(d) === 0) {
1323
- const dm = state.byName.get(d);
1324
- if (dm && !state.placed.has(d)) queue.push(dm);
1325
- }
1413
+ const current = state.inDeg.get(d) ?? 0;
1414
+ state.inDeg.set(d, current - 1);
1415
+ if (current - 1 === 0) enqueueByName(d, queue, state);
1326
1416
  }
1327
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
+ }
1328
1431
  function appendRemaining(items, result, placed) {
1329
1432
  for (const item of items) {
1330
1433
  if (item.name && !placed.has(item.name)) result.push(item);
@@ -1802,7 +1905,7 @@ function checkModuleEdge(options) {
1802
1905
  return;
1803
1906
  }
1804
1907
  if (isShallowRelativeEntrypoint(specifier, targetFile, importee.policy)) return;
1805
- if (!allowsImport(importer.policy, importee.canonicalPath)) {
1908
+ if (!allowsCrossModuleDeclaration(importer.policy, importee.canonicalPath, node)) {
1806
1909
  context.report({
1807
1910
  node,
1808
1911
  messageId: "notAllowed",
@@ -1849,9 +1952,48 @@ function isRelativeDepthTooDeep(specifier) {
1849
1952
  const parts = specifier.slice(2).split("/").filter(Boolean);
1850
1953
  return parts.length > 2;
1851
1954
  }
1852
- function allowsImport(policy, targetMatcher) {
1853
- if (policy.imports.includes("*")) return true;
1854
- return policy.imports.some((pattern) => importPatternMatches(pattern, targetMatcher));
1955
+ function allowsCrossModuleDeclaration(policy, targetMatcher, node) {
1956
+ if (allowsImportPattern(policy.imports, targetMatcher)) return true;
1957
+ return isTypeOnlyDeclaration(node) && allowsImportPattern(policy.typeImports, targetMatcher);
1958
+ }
1959
+ function isTypeOnlyDeclaration(node) {
1960
+ if (node.type === "ImportDeclaration") return isTypeOnlyImportDeclaration(node);
1961
+ if (node.type === "ExportAllDeclaration") return getExportKind(node) === "type";
1962
+ return isTypeOnlyExportNamedDeclaration(node);
1963
+ }
1964
+ function isTypeOnlyImportDeclaration(node) {
1965
+ if (getImportKind(node) === "type") return true;
1966
+ if (node.specifiers.length === 0) return false;
1967
+ return node.specifiers.every(isTypeOnlyImportSpecifier);
1968
+ }
1969
+ function isTypeOnlyImportSpecifier(specifier) {
1970
+ return specifier.type === "ImportSpecifier" && getImportKind(specifier) === "type";
1971
+ }
1972
+ function isTypeOnlyExportNamedDeclaration(node) {
1973
+ if (getExportKind(node) === "type") return true;
1974
+ if (node.specifiers.length === 0) return false;
1975
+ return node.specifiers.every(isTypeOnlyExportSpecifier);
1976
+ }
1977
+ function isTypeOnlyExportSpecifier(specifier) {
1978
+ return getExportKind(specifier) === "type";
1979
+ }
1980
+ function getImportKind(value) {
1981
+ return getDeclarationKind(value, "importKind");
1982
+ }
1983
+ function getExportKind(value) {
1984
+ return getDeclarationKind(value, "exportKind");
1985
+ }
1986
+ function getDeclarationKind(value, key) {
1987
+ if (!isRecord2(value)) return void 0;
1988
+ const kind = value[key];
1989
+ return kind === "type" || kind === "value" ? kind : void 0;
1990
+ }
1991
+ function isRecord2(value) {
1992
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1993
+ }
1994
+ function allowsImportPattern(patterns, targetMatcher) {
1995
+ if (patterns.includes("*")) return true;
1996
+ return patterns.some((pattern) => importPatternMatches(pattern, targetMatcher));
1855
1997
  }
1856
1998
  function importPatternMatches(pattern, target) {
1857
1999
  if (pattern === target) return true;
@@ -2068,7 +2210,7 @@ function isEntrypointFile(filePath, entrypoints) {
2068
2210
  }
2069
2211
 
2070
2212
  // src/rules/no-single-use-constants/index.ts
2071
- var import_typescript4 = __toESM(require("typescript"), 1);
2213
+ var import_typescript5 = __toESM(require("typescript"), 1);
2072
2214
  var no_single_use_constants_default = {
2073
2215
  meta: {
2074
2216
  type: "suggestion",
@@ -2151,11 +2293,7 @@ function extractConstDeclarators(stmt) {
2151
2293
  function isExcludedInitializer(declarator) {
2152
2294
  const init = declarator.init;
2153
2295
  if (init === null || init === void 0) return true;
2154
- return init.type === "ObjectExpression" || init.type === "NewExpression" || init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression" || init.type === "ClassExpression" || hasExplicitTypeArguments(init);
2155
- }
2156
- function hasExplicitTypeArguments(node) {
2157
- if (node.type !== "CallExpression") return false;
2158
- 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";
2159
2297
  }
2160
2298
  function collectExportedNames(program, names) {
2161
2299
  for (const stmt of program.body) {
@@ -2195,65 +2333,124 @@ function countExportedUses(name, filename, tsContext) {
2195
2333
  if (targetSymbol === void 0) return void 0;
2196
2334
  return countProjectWideUses(targetSymbol, tsContext.checker, tsContext.program);
2197
2335
  }
2198
- function findTsSourceFile(program, filename) {
2199
- const normalized = normalizeResolvedPath2(filename);
2200
- return program.getSourceFiles().find((sf) => normalizeResolvedPath2(sf.fileName) === normalized);
2201
- }
2202
2336
  function findDeclarationSymbol(sourceFile, name, checker) {
2203
2337
  for (const stmt of sourceFile.statements) {
2204
- if (!import_typescript4.default.isVariableStatement(stmt)) continue;
2205
- 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;
2206
2340
  for (const decl of stmt.declarationList.declarations) {
2207
- 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;
2208
2342
  return checker.getSymbolAtLocation(decl.name);
2209
2343
  }
2210
2344
  }
2211
2345
  return void 0;
2212
2346
  }
2213
- function countProjectWideUses(targetSymbol, checker, program) {
2214
- let total = 0;
2215
- for (const sf of program.getSourceFiles()) {
2216
- if (sf.isDeclarationFile) continue;
2217
- total += countUsesInFile(sf, targetSymbol, checker);
2218
- }
2219
- return total;
2220
- }
2221
- function countUsesInFile(sourceFile, targetSymbol, checker) {
2222
- let count = 0;
2223
- function walk(node) {
2224
- if (import_typescript4.default.isIdentifier(node) && isCountableUse(node, targetSymbol, checker)) {
2225
- 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'
2226
2361
  }
2227
- 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
+ };
2228
2386
  }
2229
- walk(sourceFile);
2230
- 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;
2231
2395
  }
2232
- function isCountableUse(node, targetSymbol, checker) {
2233
- if (!isRealUsagePosition(node)) return false;
2234
- const sym = checker.getSymbolAtLocation(node);
2235
- if (sym === void 0) return false;
2236
- 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");
2237
2405
  }
2238
- function isRealUsagePosition(node) {
2239
- const parent = node.parent;
2240
- if (isDeclarationBinding(parent, node)) return false;
2241
- if (isImportPosition(parent, node)) return false;
2242
- if (import_typescript4.default.isExportSpecifier(parent)) return false;
2243
- if (import_typescript4.default.isExportAssignment(parent)) return false;
2244
- 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
+ });
2245
2430
  }
2246
- function isDeclarationBinding(parent, node) {
2247
- 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;
2248
2440
  }
2249
- function isImportPosition(parent, node) {
2250
- if (import_typescript4.default.isImportSpecifier(parent)) return true;
2251
- if (import_typescript4.default.isImportClause(parent) && parent.name === node) return true;
2252
- 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;
2253
2446
  }
2254
- function resolveCanonicalSymbol(symbol, checker) {
2255
- if ((symbol.flags & import_typescript4.default.SymbolFlags.Alias) === 0) return symbol;
2256
- 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);
2257
2454
  }
2258
2455
 
2259
2456
  // src/rules/index.ts
@@ -2265,7 +2462,8 @@ var rules_default = {
2265
2462
  "import-control": import_control_default,
2266
2463
  "no-whitebox-testing": no_whitebox_testing_default,
2267
2464
  "export-control": export_control_default,
2268
- "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
2269
2467
  };
2270
2468
 
2271
2469
  // src/index.ts
@@ -2296,6 +2494,7 @@ var full = {
2296
2494
  "unslop/export-control": "error",
2297
2495
  "unslop/no-false-sharing": "error",
2298
2496
  "unslop/no-single-use-constants": "error",
2497
+ "unslop/no-unused-types": "error",
2299
2498
  "unslop/read-friendly-order": "error"
2300
2499
  }
2301
2500
  };