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 +9 -5
- package/dist/index.cjs +277 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +277 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,13 +85,16 @@ Each value is a policy object:
|
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
{
|
|
88
|
-
imports?: string[]
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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.
|
|
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
|
|
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: {
|
|
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 (
|
|
1012
|
+
if (import_typescript4.default.isImportDeclaration(statement)) {
|
|
950
1013
|
addImportDeclarationUsages(statement, sourceFile, projectContext, usages);
|
|
951
1014
|
continue;
|
|
952
1015
|
}
|
|
953
|
-
if (
|
|
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 || !
|
|
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 || !
|
|
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 (!
|
|
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 || !
|
|
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 &
|
|
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(
|
|
1281
|
-
const
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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 (!
|
|
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
|
|
1853
|
-
if (policy.imports
|
|
1854
|
-
return
|
|
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
|
|
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" ||
|
|
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 (!
|
|
2205
|
-
if ((stmt.declarationList.flags &
|
|
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 (!
|
|
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
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2230
|
-
|
|
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
|
|
2233
|
-
if (
|
|
2234
|
-
const
|
|
2235
|
-
if (
|
|
2236
|
-
|
|
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
|
|
2239
|
-
const
|
|
2240
|
-
if (
|
|
2241
|
-
|
|
2242
|
-
if (
|
|
2243
|
-
|
|
2244
|
-
return
|
|
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
|
|
2247
|
-
|
|
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
|
|
2250
|
-
if (
|
|
2251
|
-
|
|
2252
|
-
|
|
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
|
|
2255
|
-
|
|
2256
|
-
return
|
|
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
|
};
|