unguard 0.13.2 → 0.13.3
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/{chunk-2QER6GG7.js → chunk-WUH4JLMB.js} +265 -346
- package/dist/chunk-WUH4JLMB.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-2QER6GG7.js.map +0 -1
|
@@ -653,7 +653,8 @@ var repeatedLiteralProperty = {
|
|
|
653
653
|
}
|
|
654
654
|
const line = ts8.getLineAndCharacterOfPosition(sourceFile, prop.getStart(sourceFile)).line + 1;
|
|
655
655
|
const scopeId = findEnclosingFunctionStart(prop, sourceFile);
|
|
656
|
-
|
|
656
|
+
const key = ts8.isIdentifier(prop.name) ? prop.name.text : ts8.isStringLiteral(prop.name) ? prop.name.text : "";
|
|
657
|
+
list.push({ line, isAsConst: result.isAsConst, scopeId, key });
|
|
657
658
|
}
|
|
658
659
|
}
|
|
659
660
|
ts8.forEachChild(node, visit2);
|
|
@@ -662,6 +663,8 @@ var repeatedLiteralProperty = {
|
|
|
662
663
|
const valueMap = /* @__PURE__ */ new Map();
|
|
663
664
|
ts8.forEachChild(sourceFile, visit2);
|
|
664
665
|
for (const [value, occurrences] of valueMap) {
|
|
666
|
+
const uniqueKeys = new Set(occurrences.map((o) => o.key).filter((k) => k !== ""));
|
|
667
|
+
if (uniqueKeys.size === 1) continue;
|
|
665
668
|
const hasAsConst = occurrences.some((o) => o.isAsConst);
|
|
666
669
|
const uniqueScopes = new Set(occurrences.map((o) => o.scopeId));
|
|
667
670
|
const threshold = hasAsConst ? 3 : 5;
|
|
@@ -753,7 +756,7 @@ var repeatedReturnShape = {
|
|
|
753
756
|
const shapeMap = /* @__PURE__ */ new Map();
|
|
754
757
|
for (const [file, { sourceFile }] of project.files) {
|
|
755
758
|
let visit2 = function(node) {
|
|
756
|
-
if (isFunctionLike(node)) {
|
|
759
|
+
if (isFunctionLike(node) && !isCallbackArgument(node)) {
|
|
757
760
|
const functionName = deriveFunctionName(node, sourceFile);
|
|
758
761
|
const line = ts10.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
|
|
759
762
|
collectReturnShapes(node, file, line, functionName, sourceFile, shapeMap);
|
|
@@ -763,7 +766,9 @@ var repeatedReturnShape = {
|
|
|
763
766
|
var visit = visit2;
|
|
764
767
|
ts10.forEachChild(sourceFile, visit2);
|
|
765
768
|
}
|
|
766
|
-
|
|
769
|
+
const knownTypeShapes = buildKnownTypeShapes(project.types.getAll());
|
|
770
|
+
for (const [shapeKey, entries] of shapeMap) {
|
|
771
|
+
if (knownTypeShapes.has(shapeKey)) continue;
|
|
767
772
|
const byFunction = /* @__PURE__ */ new Map();
|
|
768
773
|
for (const entry of entries) {
|
|
769
774
|
const key = `${entry.file}:${entry.line}`;
|
|
@@ -773,6 +778,8 @@ var repeatedReturnShape = {
|
|
|
773
778
|
}
|
|
774
779
|
const unique = [...byFunction.values()];
|
|
775
780
|
if (unique.length < THRESHOLD) continue;
|
|
781
|
+
const uniqueFiles = new Set(unique.map((e) => e.file));
|
|
782
|
+
if (uniqueFiles.size < 2) continue;
|
|
776
783
|
const sorted = unique.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
|
|
777
784
|
const first = sorted[0];
|
|
778
785
|
if (first === void 0) continue;
|
|
@@ -832,6 +839,43 @@ function addShape(objLiteral, file, funcLine, functionName, sourceFile, shapeMap
|
|
|
832
839
|
const { sorted, list } = getShapeGroup(shapeMap, props);
|
|
833
840
|
list.push({ file, line: funcLine, functionName, props: sorted });
|
|
834
841
|
}
|
|
842
|
+
function isCallbackArgument(node) {
|
|
843
|
+
let current = node;
|
|
844
|
+
if (ts10.isParenthesizedExpression(current.parent)) {
|
|
845
|
+
current = current.parent;
|
|
846
|
+
}
|
|
847
|
+
const parent = current.parent;
|
|
848
|
+
if (!ts10.isCallExpression(parent)) return false;
|
|
849
|
+
return parent.arguments.some((arg) => arg === current);
|
|
850
|
+
}
|
|
851
|
+
function buildKnownTypeShapes(typeEntries) {
|
|
852
|
+
const shapes = /* @__PURE__ */ new Set();
|
|
853
|
+
for (const entry of typeEntries) {
|
|
854
|
+
const props = extractTypePropertyNames(entry.node);
|
|
855
|
+
if (props && props.length >= 2) {
|
|
856
|
+
shapes.add([...props].sort().join("\0"));
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
return shapes;
|
|
860
|
+
}
|
|
861
|
+
function extractTypePropertyNames(node) {
|
|
862
|
+
let members;
|
|
863
|
+
if (ts10.isTypeLiteralNode(node)) {
|
|
864
|
+
members = node.members;
|
|
865
|
+
} else if (ts10.isInterfaceDeclaration(node)) {
|
|
866
|
+
members = node.members;
|
|
867
|
+
}
|
|
868
|
+
if (!members) return null;
|
|
869
|
+
const names = [];
|
|
870
|
+
for (const member of members) {
|
|
871
|
+
if (ts10.isPropertySignature(member) && member.name) {
|
|
872
|
+
if (ts10.isIdentifier(member.name)) names.push(member.name.text);
|
|
873
|
+
else if (ts10.isStringLiteral(member.name)) names.push(member.name.text);
|
|
874
|
+
else return null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
return names.length > 0 ? names : null;
|
|
878
|
+
}
|
|
835
879
|
function deriveFunctionName(node, sourceFile) {
|
|
836
880
|
if (ts10.isFunctionDeclaration(node) && node.name) {
|
|
837
881
|
return node.name.text;
|
|
@@ -1290,174 +1334,15 @@ function hasOptionalChaining(node) {
|
|
|
1290
1334
|
return false;
|
|
1291
1335
|
}
|
|
1292
1336
|
|
|
1293
|
-
// src/rules/ts/no-module-state-write.ts
|
|
1294
|
-
import * as ts21 from "typescript";
|
|
1295
|
-
var arrayMutators = /* @__PURE__ */ new Set([
|
|
1296
|
-
"copyWithin",
|
|
1297
|
-
"fill",
|
|
1298
|
-
"pop",
|
|
1299
|
-
"push",
|
|
1300
|
-
"reverse",
|
|
1301
|
-
"shift",
|
|
1302
|
-
"sort",
|
|
1303
|
-
"splice",
|
|
1304
|
-
"unshift"
|
|
1305
|
-
]);
|
|
1306
|
-
var mapMutators = /* @__PURE__ */ new Set(["clear", "delete", "set"]);
|
|
1307
|
-
var setMutators = /* @__PURE__ */ new Set(["add", "clear", "delete"]);
|
|
1308
|
-
var noModuleStateWrite = {
|
|
1309
|
-
kind: "ts",
|
|
1310
|
-
id: "no-module-state-write",
|
|
1311
|
-
severity: "warning",
|
|
1312
|
-
message: "Function mutates module-scope state; make the dependency explicit instead of writing ambient state",
|
|
1313
|
-
visit(node, ctx) {
|
|
1314
|
-
if (getEnclosingFunctionLike(node) === null) return;
|
|
1315
|
-
const bindingName = getAmbientWriteBinding(node, ctx);
|
|
1316
|
-
if (!bindingName) return;
|
|
1317
|
-
ctx.report(node, `Function mutates module-scope state through "${bindingName}"`);
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
1320
|
-
function getAmbientWriteBinding(node, ctx) {
|
|
1321
|
-
if (ts21.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
|
|
1322
|
-
return getAmbientBindingFromWriteTarget(node.left, ctx);
|
|
1323
|
-
}
|
|
1324
|
-
if (ts21.isPrefixUnaryExpression(node)) {
|
|
1325
|
-
if (node.operator === ts21.SyntaxKind.PlusPlusToken || node.operator === ts21.SyntaxKind.MinusMinusToken) {
|
|
1326
|
-
return getAmbientBindingFromWriteTarget(node.operand, ctx);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
if (ts21.isPostfixUnaryExpression(node)) {
|
|
1330
|
-
if (node.operator === ts21.SyntaxKind.PlusPlusToken || node.operator === ts21.SyntaxKind.MinusMinusToken) {
|
|
1331
|
-
return getAmbientBindingFromWriteTarget(node.operand, ctx);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
if (ts21.isDeleteExpression(node)) {
|
|
1335
|
-
return getAmbientBindingFromWriteTarget(node.expression, ctx);
|
|
1336
|
-
}
|
|
1337
|
-
if (ts21.isCallExpression(node)) {
|
|
1338
|
-
return getAmbientBindingFromMutatorCall(node, ctx);
|
|
1339
|
-
}
|
|
1340
|
-
return null;
|
|
1341
|
-
}
|
|
1342
|
-
function getAmbientBindingFromWriteTarget(node, ctx) {
|
|
1343
|
-
const target = unwrapExpression2(node);
|
|
1344
|
-
if (ts21.isIdentifier(target)) {
|
|
1345
|
-
return isAmbientBinding(target, ctx) ? target.text : null;
|
|
1346
|
-
}
|
|
1347
|
-
if (ts21.isPropertyAccessExpression(target) || ts21.isElementAccessExpression(target)) {
|
|
1348
|
-
const root = getRootIdentifier(target.expression);
|
|
1349
|
-
if (!root) return null;
|
|
1350
|
-
return isAmbientBinding(root, ctx) ? root.text : null;
|
|
1351
|
-
}
|
|
1352
|
-
return null;
|
|
1353
|
-
}
|
|
1354
|
-
function getAmbientBindingFromMutatorCall(node, ctx) {
|
|
1355
|
-
const callee = unwrapExpression2(node.expression);
|
|
1356
|
-
if (!ts21.isPropertyAccessExpression(callee)) return null;
|
|
1357
|
-
const receiver = unwrapExpression2(callee.expression);
|
|
1358
|
-
const root = getRootIdentifier(receiver);
|
|
1359
|
-
if (!root || !isAmbientBinding(root, ctx)) return null;
|
|
1360
|
-
const receiverType = ctx.checker.getTypeAtLocation(receiver);
|
|
1361
|
-
const method = callee.name.text;
|
|
1362
|
-
if (!isKnownMutator(receiverType, method, ctx.checker)) return null;
|
|
1363
|
-
return root.text;
|
|
1364
|
-
}
|
|
1365
|
-
function isAmbientBinding(node, ctx) {
|
|
1366
|
-
const symbol = ctx.checker.getSymbolAtLocation(node);
|
|
1367
|
-
if (!symbol) return false;
|
|
1368
|
-
for (const declaration of symbol.declarations ?? []) {
|
|
1369
|
-
if (!isAmbientBindingDeclaration(declaration, ctx.sourceFile)) continue;
|
|
1370
|
-
return true;
|
|
1371
|
-
}
|
|
1372
|
-
return false;
|
|
1373
|
-
}
|
|
1374
|
-
function isAmbientBindingDeclaration(node, sourceFile) {
|
|
1375
|
-
if (node.getSourceFile() !== sourceFile) return false;
|
|
1376
|
-
if (getEnclosingFunctionLike(node) !== null) return false;
|
|
1377
|
-
return ts21.isVariableDeclaration(node) || ts21.isBindingElement(node) || ts21.isImportClause(node) || ts21.isImportSpecifier(node) || ts21.isNamespaceImport(node) || ts21.isImportEqualsDeclaration(node);
|
|
1378
|
-
}
|
|
1379
|
-
function getRootIdentifier(node) {
|
|
1380
|
-
const target = unwrapExpression2(node);
|
|
1381
|
-
if (ts21.isIdentifier(target)) return target;
|
|
1382
|
-
if (ts21.isPropertyAccessExpression(target) || ts21.isElementAccessExpression(target)) {
|
|
1383
|
-
return getRootIdentifier(target.expression);
|
|
1384
|
-
}
|
|
1385
|
-
return null;
|
|
1386
|
-
}
|
|
1387
|
-
function unwrapExpression2(node) {
|
|
1388
|
-
let current = node;
|
|
1389
|
-
while (true) {
|
|
1390
|
-
if (ts21.isParenthesizedExpression(current)) {
|
|
1391
|
-
current = current.expression;
|
|
1392
|
-
continue;
|
|
1393
|
-
}
|
|
1394
|
-
if (ts21.isAsExpression(current) || ts21.isTypeAssertionExpression(current) || ts21.isNonNullExpression(current)) {
|
|
1395
|
-
current = current.expression;
|
|
1396
|
-
continue;
|
|
1397
|
-
}
|
|
1398
|
-
if (ts21.isSatisfiesExpression(current)) {
|
|
1399
|
-
current = current.expression;
|
|
1400
|
-
continue;
|
|
1401
|
-
}
|
|
1402
|
-
return current;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
function isAssignmentOperator(kind) {
|
|
1406
|
-
switch (kind) {
|
|
1407
|
-
case ts21.SyntaxKind.EqualsToken:
|
|
1408
|
-
case ts21.SyntaxKind.PlusEqualsToken:
|
|
1409
|
-
case ts21.SyntaxKind.MinusEqualsToken:
|
|
1410
|
-
case ts21.SyntaxKind.AsteriskEqualsToken:
|
|
1411
|
-
case ts21.SyntaxKind.AsteriskAsteriskEqualsToken:
|
|
1412
|
-
case ts21.SyntaxKind.SlashEqualsToken:
|
|
1413
|
-
case ts21.SyntaxKind.PercentEqualsToken:
|
|
1414
|
-
case ts21.SyntaxKind.AmpersandEqualsToken:
|
|
1415
|
-
case ts21.SyntaxKind.BarEqualsToken:
|
|
1416
|
-
case ts21.SyntaxKind.CaretEqualsToken:
|
|
1417
|
-
case ts21.SyntaxKind.LessThanLessThanEqualsToken:
|
|
1418
|
-
case ts21.SyntaxKind.GreaterThanGreaterThanEqualsToken:
|
|
1419
|
-
case ts21.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken:
|
|
1420
|
-
case ts21.SyntaxKind.BarBarEqualsToken:
|
|
1421
|
-
case ts21.SyntaxKind.AmpersandAmpersandEqualsToken:
|
|
1422
|
-
case ts21.SyntaxKind.QuestionQuestionEqualsToken:
|
|
1423
|
-
return true;
|
|
1424
|
-
default:
|
|
1425
|
-
return false;
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
function getEnclosingFunctionLike(node) {
|
|
1429
|
-
let current = node.parent;
|
|
1430
|
-
while (current) {
|
|
1431
|
-
if (ts21.isFunctionLike(current)) return current;
|
|
1432
|
-
current = current.parent;
|
|
1433
|
-
}
|
|
1434
|
-
return null;
|
|
1435
|
-
}
|
|
1436
|
-
function isKnownMutator(type, method, checker) {
|
|
1437
|
-
const apparent = checker.getApparentType(type);
|
|
1438
|
-
if (checker.isArrayType(apparent) || checker.isTupleType(apparent)) {
|
|
1439
|
-
return arrayMutators.has(method);
|
|
1440
|
-
}
|
|
1441
|
-
const symbol = apparent.getSymbol();
|
|
1442
|
-
const name = symbol?.getName();
|
|
1443
|
-
if (name === "Map" || name === "WeakMap") {
|
|
1444
|
-
return mapMutators.has(method);
|
|
1445
|
-
}
|
|
1446
|
-
if (name === "Set" || name === "WeakSet") {
|
|
1447
|
-
return setMutators.has(method);
|
|
1448
|
-
}
|
|
1449
|
-
return false;
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
1337
|
// src/rules/ts/no-non-null-assertion.ts
|
|
1453
|
-
import * as
|
|
1338
|
+
import * as ts21 from "typescript";
|
|
1454
1339
|
var noNonNullAssertion = {
|
|
1455
1340
|
kind: "ts",
|
|
1456
1341
|
id: "no-non-null-assertion",
|
|
1457
1342
|
severity: "warning",
|
|
1458
1343
|
message: "Non-null assertion (!) overrides the type checker; narrow with a type guard or fix the type so it's not nullable",
|
|
1459
1344
|
visit(node, ctx) {
|
|
1460
|
-
if (!
|
|
1345
|
+
if (!ts21.isNonNullExpression(node)) return;
|
|
1461
1346
|
const inner = node.expression;
|
|
1462
1347
|
if (!ctx.isNullable(inner)) return;
|
|
1463
1348
|
if (ctx.isExternal(inner)) return;
|
|
@@ -1468,32 +1353,32 @@ var noNonNullAssertion = {
|
|
|
1468
1353
|
}
|
|
1469
1354
|
};
|
|
1470
1355
|
function isSplitElementAccess(node) {
|
|
1471
|
-
if (!
|
|
1356
|
+
if (!ts21.isElementAccessExpression(node)) return false;
|
|
1472
1357
|
const obj = node.expression;
|
|
1473
|
-
if (!
|
|
1358
|
+
if (!ts21.isCallExpression(obj)) return false;
|
|
1474
1359
|
const callee = obj.expression;
|
|
1475
|
-
if (!
|
|
1360
|
+
if (!ts21.isPropertyAccessExpression(callee)) return false;
|
|
1476
1361
|
return callee.name.text === "split";
|
|
1477
1362
|
}
|
|
1478
1363
|
function isFilterElementAccess(node) {
|
|
1479
|
-
if (
|
|
1364
|
+
if (ts21.isElementAccessExpression(node)) {
|
|
1480
1365
|
const obj = node.expression;
|
|
1481
|
-
if (
|
|
1366
|
+
if (ts21.isCallExpression(obj)) {
|
|
1482
1367
|
const callee = obj.expression;
|
|
1483
|
-
if (
|
|
1368
|
+
if (ts21.isPropertyAccessExpression(callee) && callee.name.text === "filter") return true;
|
|
1484
1369
|
}
|
|
1485
|
-
if (
|
|
1370
|
+
if (ts21.isIdentifier(obj)) {
|
|
1486
1371
|
const init = findVariableInit(obj);
|
|
1487
|
-
if (init &&
|
|
1372
|
+
if (init && ts21.isCallExpression(init)) {
|
|
1488
1373
|
const callee = init.expression;
|
|
1489
|
-
if (
|
|
1374
|
+
if (ts21.isPropertyAccessExpression(callee) && callee.name.text === "filter") return true;
|
|
1490
1375
|
}
|
|
1491
1376
|
}
|
|
1492
1377
|
}
|
|
1493
1378
|
return false;
|
|
1494
1379
|
}
|
|
1495
1380
|
function isLengthGuardedAccess(node) {
|
|
1496
|
-
if (!
|
|
1381
|
+
if (!ts21.isElementAccessExpression(node)) return false;
|
|
1497
1382
|
const arr = node.expression;
|
|
1498
1383
|
const arrName = getIdentifierName(arr);
|
|
1499
1384
|
if (!arrName) return false;
|
|
@@ -1504,25 +1389,25 @@ function isInsideForLoopBoundedBy(node, arrName) {
|
|
|
1504
1389
|
let current = node;
|
|
1505
1390
|
while (current.parent) {
|
|
1506
1391
|
current = current.parent;
|
|
1507
|
-
if (
|
|
1392
|
+
if (ts21.isForStatement(current) && current.condition) {
|
|
1508
1393
|
if (isLengthBoundCondition(current.condition, arrName)) return true;
|
|
1509
1394
|
}
|
|
1510
1395
|
}
|
|
1511
1396
|
return false;
|
|
1512
1397
|
}
|
|
1513
1398
|
function isLengthBoundCondition(cond, arrName) {
|
|
1514
|
-
if (!
|
|
1399
|
+
if (!ts21.isBinaryExpression(cond)) return false;
|
|
1515
1400
|
const op = cond.operatorToken.kind;
|
|
1516
|
-
if (op ===
|
|
1401
|
+
if (op === ts21.SyntaxKind.LessThanToken || op === ts21.SyntaxKind.LessThanEqualsToken) {
|
|
1517
1402
|
return isLengthAccess(cond.right, arrName);
|
|
1518
1403
|
}
|
|
1519
|
-
if (op ===
|
|
1404
|
+
if (op === ts21.SyntaxKind.GreaterThanToken || op === ts21.SyntaxKind.GreaterThanEqualsToken) {
|
|
1520
1405
|
return isLengthAccess(cond.left, arrName);
|
|
1521
1406
|
}
|
|
1522
1407
|
return false;
|
|
1523
1408
|
}
|
|
1524
1409
|
function isLengthAccess(node, arrName) {
|
|
1525
|
-
if (!
|
|
1410
|
+
if (!ts21.isPropertyAccessExpression(node)) return false;
|
|
1526
1411
|
if (node.name.text !== "length") return false;
|
|
1527
1412
|
return getIdentifierName(node.expression) === arrName;
|
|
1528
1413
|
}
|
|
@@ -1530,16 +1415,16 @@ function hasPrecedingLengthGuard(node, arrName) {
|
|
|
1530
1415
|
let current = node;
|
|
1531
1416
|
while (current.parent) {
|
|
1532
1417
|
const parent = current.parent;
|
|
1533
|
-
if (
|
|
1418
|
+
if (ts21.isBlock(parent)) {
|
|
1534
1419
|
for (const stmt of parent.statements) {
|
|
1535
1420
|
if (stmt === current || stmt.pos >= current.pos) break;
|
|
1536
|
-
if (
|
|
1421
|
+
if (ts21.isIfStatement(stmt) && isLengthGuardWithEarlyExit(stmt, arrName)) return true;
|
|
1537
1422
|
}
|
|
1538
1423
|
}
|
|
1539
|
-
if (
|
|
1424
|
+
if (ts21.isIfStatement(parent) && parent.thenStatement === current) {
|
|
1540
1425
|
if (isPositiveLengthCheck(parent.expression, arrName)) return true;
|
|
1541
1426
|
}
|
|
1542
|
-
if (
|
|
1427
|
+
if (ts21.isBlock(current) && ts21.isIfStatement(parent) && parent.thenStatement === current) {
|
|
1543
1428
|
if (isPositiveLengthCheck(parent.expression, arrName)) return true;
|
|
1544
1429
|
}
|
|
1545
1430
|
current = parent;
|
|
@@ -1551,79 +1436,79 @@ function isLengthGuardWithEarlyExit(stmt, arrName) {
|
|
|
1551
1436
|
return isZeroLengthCheck(stmt.expression, arrName);
|
|
1552
1437
|
}
|
|
1553
1438
|
function isZeroLengthCheck(expr, arrName) {
|
|
1554
|
-
if (!
|
|
1439
|
+
if (!ts21.isBinaryExpression(expr)) return false;
|
|
1555
1440
|
const op = expr.operatorToken.kind;
|
|
1556
|
-
if (op ===
|
|
1441
|
+
if (op === ts21.SyntaxKind.EqualsEqualsEqualsToken || op === ts21.SyntaxKind.EqualsEqualsToken) {
|
|
1557
1442
|
if (isLengthAccess(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;
|
|
1558
1443
|
if (isLengthAccess(expr.right, arrName) && isNumericLiteralValue(expr.left, 0)) return true;
|
|
1559
1444
|
}
|
|
1560
|
-
if (op ===
|
|
1445
|
+
if (op === ts21.SyntaxKind.LessThanToken) {
|
|
1561
1446
|
if (isLengthAccess(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;
|
|
1562
1447
|
}
|
|
1563
|
-
if (op ===
|
|
1448
|
+
if (op === ts21.SyntaxKind.ExclamationEqualsEqualsToken || op === ts21.SyntaxKind.ExclamationEqualsToken) {
|
|
1564
1449
|
if (isLengthAccess(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;
|
|
1565
1450
|
}
|
|
1566
1451
|
return false;
|
|
1567
1452
|
}
|
|
1568
1453
|
function isPositiveLengthCheck(expr, arrName) {
|
|
1569
|
-
if (!
|
|
1454
|
+
if (!ts21.isBinaryExpression(expr)) return false;
|
|
1570
1455
|
const op = expr.operatorToken.kind;
|
|
1571
|
-
if (op ===
|
|
1456
|
+
if (op === ts21.SyntaxKind.GreaterThanToken) {
|
|
1572
1457
|
if (isLengthAccess(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;
|
|
1573
1458
|
}
|
|
1574
|
-
if (op ===
|
|
1459
|
+
if (op === ts21.SyntaxKind.GreaterThanEqualsToken) {
|
|
1575
1460
|
if (isLengthAccess(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;
|
|
1576
1461
|
}
|
|
1577
|
-
if (op ===
|
|
1462
|
+
if (op === ts21.SyntaxKind.ExclamationEqualsEqualsToken || op === ts21.SyntaxKind.ExclamationEqualsToken) {
|
|
1578
1463
|
if (isLengthAccess(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;
|
|
1579
1464
|
}
|
|
1580
1465
|
return false;
|
|
1581
1466
|
}
|
|
1582
1467
|
function isEarlyExit(stmt) {
|
|
1583
|
-
if (
|
|
1584
|
-
if (
|
|
1468
|
+
if (ts21.isReturnStatement(stmt) || ts21.isThrowStatement(stmt)) return true;
|
|
1469
|
+
if (ts21.isBlock(stmt) && stmt.statements.length === 1) {
|
|
1585
1470
|
const inner = stmt.statements[0];
|
|
1586
1471
|
if (inner === void 0) return false;
|
|
1587
|
-
return
|
|
1472
|
+
return ts21.isReturnStatement(inner) || ts21.isThrowStatement(inner);
|
|
1588
1473
|
}
|
|
1589
1474
|
return false;
|
|
1590
1475
|
}
|
|
1591
1476
|
function isNumericLiteralValue(node, value) {
|
|
1592
|
-
return
|
|
1477
|
+
return ts21.isNumericLiteral(node) && node.text === String(value);
|
|
1593
1478
|
}
|
|
1594
1479
|
function isNumericLiteralGte(node, min) {
|
|
1595
|
-
return
|
|
1480
|
+
return ts21.isNumericLiteral(node) && Number(node.text) >= min;
|
|
1596
1481
|
}
|
|
1597
1482
|
function getIdentifierName(node) {
|
|
1598
|
-
if (
|
|
1483
|
+
if (ts21.isIdentifier(node)) return node.text;
|
|
1599
1484
|
return null;
|
|
1600
1485
|
}
|
|
1601
1486
|
function findVariableInit(id) {
|
|
1602
1487
|
const sourceFile = id.getSourceFile();
|
|
1603
1488
|
let result;
|
|
1604
1489
|
function visit(node) {
|
|
1605
|
-
if (
|
|
1490
|
+
if (ts21.isVariableDeclaration(node) && ts21.isIdentifier(node.name) && node.name.text === id.text && node.initializer) {
|
|
1606
1491
|
result = node.initializer;
|
|
1607
1492
|
}
|
|
1608
|
-
if (!result)
|
|
1493
|
+
if (!result) ts21.forEachChild(node, visit);
|
|
1609
1494
|
}
|
|
1610
1495
|
visit(sourceFile);
|
|
1611
1496
|
return result;
|
|
1612
1497
|
}
|
|
1613
1498
|
|
|
1614
1499
|
// src/rules/ts/no-null-ternary-normalization.ts
|
|
1615
|
-
import * as
|
|
1500
|
+
import * as ts22 from "typescript";
|
|
1616
1501
|
var noNullTernaryNormalization = {
|
|
1617
1502
|
kind: "ts",
|
|
1618
1503
|
id: "no-null-ternary-normalization",
|
|
1619
1504
|
severity: "warning",
|
|
1620
1505
|
message: "Ternary null-normalization (x == null ? fallback : x); if the type guarantees non-null, remove the ternary; if not, fix the type upstream",
|
|
1621
1506
|
visit(node, ctx) {
|
|
1622
|
-
if (!
|
|
1507
|
+
if (!ts22.isConditionalExpression(node)) return;
|
|
1623
1508
|
const test = node.condition;
|
|
1624
|
-
if (!
|
|
1509
|
+
if (!ts22.isBinaryExpression(test)) return;
|
|
1625
1510
|
const op = test.operatorToken.kind;
|
|
1626
|
-
if (op !==
|
|
1511
|
+
if (op !== ts22.SyntaxKind.EqualsEqualsEqualsToken && op !== ts22.SyntaxKind.ExclamationEqualsEqualsToken && op !== ts22.SyntaxKind.EqualsEqualsToken && op !== ts22.SyntaxKind.ExclamationEqualsToken) return;
|
|
1627
1512
|
const hasNullishComparand = isNullish(test.left) || isNullish(test.right);
|
|
1628
1513
|
if (!hasNullishComparand) return;
|
|
1629
1514
|
if (isNullish(node.whenTrue) || isNullish(node.whenFalse)) {
|
|
@@ -1637,35 +1522,35 @@ var noNullTernaryNormalization = {
|
|
|
1637
1522
|
}
|
|
1638
1523
|
};
|
|
1639
1524
|
function isNullish(node) {
|
|
1640
|
-
if (node.kind ===
|
|
1641
|
-
if (
|
|
1642
|
-
if (
|
|
1525
|
+
if (node.kind === ts22.SyntaxKind.NullKeyword) return true;
|
|
1526
|
+
if (ts22.isIdentifier(node) && node.text === "undefined") return true;
|
|
1527
|
+
if (ts22.isVoidExpression(node)) return true;
|
|
1643
1528
|
return false;
|
|
1644
1529
|
}
|
|
1645
1530
|
|
|
1646
1531
|
// src/rules/ts/no-nullish-coalescing.ts
|
|
1647
|
-
import * as
|
|
1532
|
+
import * as ts23 from "typescript";
|
|
1648
1533
|
var noNullishCoalescing = {
|
|
1649
1534
|
kind: "ts",
|
|
1650
1535
|
id: "no-nullish-coalescing",
|
|
1651
1536
|
severity: "warning",
|
|
1652
1537
|
message: "Nullish coalescing (??) on a non-nullable type is unreachable; remove the fallback or fix the type upstream",
|
|
1653
1538
|
visit(node, ctx) {
|
|
1654
|
-
if (!
|
|
1655
|
-
if (node.operatorToken.kind !==
|
|
1539
|
+
if (!ts23.isBinaryExpression(node)) return;
|
|
1540
|
+
if (node.operatorToken.kind !== ts23.SyntaxKind.QuestionQuestionToken) return;
|
|
1656
1541
|
if (isPossiblyMissingArrayBindingValue(node.left, ctx)) return;
|
|
1657
1542
|
if (ctx.isNullable(node.left)) return;
|
|
1658
1543
|
ctx.report(node);
|
|
1659
1544
|
}
|
|
1660
1545
|
};
|
|
1661
1546
|
function isPossiblyMissingArrayBindingValue(node, ctx) {
|
|
1662
|
-
if (!
|
|
1547
|
+
if (!ts23.isIdentifier(node)) return false;
|
|
1663
1548
|
const symbol = ctx.checker.getSymbolAtLocation(node);
|
|
1664
1549
|
if (!symbol) return false;
|
|
1665
1550
|
for (const declaration of symbol.declarations ?? []) {
|
|
1666
|
-
if (!
|
|
1551
|
+
if (!ts23.isBindingElement(declaration)) continue;
|
|
1667
1552
|
if (declaration.initializer || declaration.dotDotDotToken) continue;
|
|
1668
|
-
if (!
|
|
1553
|
+
if (!ts23.isArrayBindingPattern(declaration.parent)) continue;
|
|
1669
1554
|
const pattern = declaration.parent;
|
|
1670
1555
|
const index = pattern.elements.indexOf(declaration);
|
|
1671
1556
|
if (index < 0) continue;
|
|
@@ -1693,14 +1578,14 @@ function isTupleTypeReference(type, checker) {
|
|
|
1693
1578
|
}
|
|
1694
1579
|
|
|
1695
1580
|
// src/rules/ts/no-optional-call.ts
|
|
1696
|
-
import * as
|
|
1581
|
+
import * as ts24 from "typescript";
|
|
1697
1582
|
var noOptionalCall = {
|
|
1698
1583
|
kind: "ts",
|
|
1699
1584
|
id: "no-optional-call",
|
|
1700
1585
|
severity: "warning",
|
|
1701
1586
|
message: "Optional call (?.) on a non-nullable function is redundant; call directly or fix the type upstream",
|
|
1702
1587
|
visit(node, ctx) {
|
|
1703
|
-
if (!
|
|
1588
|
+
if (!ts24.isCallExpression(node)) return;
|
|
1704
1589
|
if (!node.questionDotToken) return;
|
|
1705
1590
|
if (ctx.isNullable(node.expression)) return;
|
|
1706
1591
|
ctx.report(node);
|
|
@@ -1708,14 +1593,14 @@ var noOptionalCall = {
|
|
|
1708
1593
|
};
|
|
1709
1594
|
|
|
1710
1595
|
// src/rules/ts/no-optional-element-access.ts
|
|
1711
|
-
import * as
|
|
1596
|
+
import * as ts25 from "typescript";
|
|
1712
1597
|
var noOptionalElementAccess = {
|
|
1713
1598
|
kind: "ts",
|
|
1714
1599
|
id: "no-optional-element-access",
|
|
1715
1600
|
severity: "warning",
|
|
1716
1601
|
message: "Optional element access (?.[]) on a non-nullable type is redundant; use direct access or fix the type upstream",
|
|
1717
1602
|
visit(node, ctx) {
|
|
1718
|
-
if (!
|
|
1603
|
+
if (!ts25.isElementAccessExpression(node)) return;
|
|
1719
1604
|
if (!node.questionDotToken) return;
|
|
1720
1605
|
if (ctx.isNullable(node.expression)) return;
|
|
1721
1606
|
ctx.report(node);
|
|
@@ -1723,14 +1608,14 @@ var noOptionalElementAccess = {
|
|
|
1723
1608
|
};
|
|
1724
1609
|
|
|
1725
1610
|
// src/rules/ts/no-optional-property-access.ts
|
|
1726
|
-
import * as
|
|
1611
|
+
import * as ts26 from "typescript";
|
|
1727
1612
|
var noOptionalPropertyAccess = {
|
|
1728
1613
|
kind: "ts",
|
|
1729
1614
|
id: "no-optional-property-access",
|
|
1730
1615
|
severity: "warning",
|
|
1731
1616
|
message: "Optional chaining (?.) on a non-nullable type is redundant; use direct access or fix the type upstream",
|
|
1732
1617
|
visit(node, ctx) {
|
|
1733
|
-
if (!
|
|
1618
|
+
if (!ts26.isPropertyAccessExpression(node)) return;
|
|
1734
1619
|
if (!node.questionDotToken) return;
|
|
1735
1620
|
if (ctx.isNullable(node.expression)) return;
|
|
1736
1621
|
ctx.report(node);
|
|
@@ -1738,27 +1623,27 @@ var noOptionalPropertyAccess = {
|
|
|
1738
1623
|
};
|
|
1739
1624
|
|
|
1740
1625
|
// src/rules/ts/no-redundant-existence-guard.ts
|
|
1741
|
-
import * as
|
|
1626
|
+
import * as ts27 from "typescript";
|
|
1742
1627
|
var noRedundantExistenceGuard = {
|
|
1743
1628
|
kind: "ts",
|
|
1744
1629
|
id: "no-redundant-existence-guard",
|
|
1745
1630
|
severity: "warning",
|
|
1746
1631
|
message: "Redundant existence guard (obj && obj.prop) on a non-nullable type; remove the guard or fix the type upstream",
|
|
1747
1632
|
visit(node, ctx) {
|
|
1748
|
-
if (!
|
|
1749
|
-
if (node.operatorToken.kind !==
|
|
1633
|
+
if (!ts27.isBinaryExpression(node)) return;
|
|
1634
|
+
if (node.operatorToken.kind !== ts27.SyntaxKind.AmpersandAmpersandToken) return;
|
|
1750
1635
|
const left = node.left;
|
|
1751
1636
|
const right = node.right;
|
|
1752
|
-
if (
|
|
1637
|
+
if (ts27.isIdentifier(left) && accessesIdentifier(right, left.text)) {
|
|
1753
1638
|
if (ctx.isNullable(left)) return;
|
|
1754
1639
|
ctx.report(node);
|
|
1755
1640
|
return;
|
|
1756
1641
|
}
|
|
1757
|
-
if (
|
|
1642
|
+
if (ts27.isBinaryExpression(left) && isNullCheck(left)) {
|
|
1758
1643
|
const checked = getNullCheckedIdentifier(left);
|
|
1759
1644
|
if (checked && accessesIdentifier(right, checked)) {
|
|
1760
|
-
const identNode =
|
|
1761
|
-
if (
|
|
1645
|
+
const identNode = ts27.isIdentifier(left.left) ? left.left : left.right;
|
|
1646
|
+
if (ts27.isIdentifier(identNode) && identNode.text === checked && !ctx.isNullable(identNode)) {
|
|
1762
1647
|
ctx.report(node);
|
|
1763
1648
|
}
|
|
1764
1649
|
}
|
|
@@ -1767,34 +1652,34 @@ var noRedundantExistenceGuard = {
|
|
|
1767
1652
|
};
|
|
1768
1653
|
function accessesIdentifier(expr, name) {
|
|
1769
1654
|
const root = getExpressionRoot(expr);
|
|
1770
|
-
return
|
|
1655
|
+
return ts27.isIdentifier(root) && root.text === name;
|
|
1771
1656
|
}
|
|
1772
1657
|
function getExpressionRoot(node) {
|
|
1773
|
-
if (
|
|
1774
|
-
if (
|
|
1775
|
-
if (
|
|
1658
|
+
if (ts27.isPropertyAccessExpression(node)) return getExpressionRoot(node.expression);
|
|
1659
|
+
if (ts27.isElementAccessExpression(node)) return getExpressionRoot(node.expression);
|
|
1660
|
+
if (ts27.isCallExpression(node)) return getExpressionRoot(node.expression);
|
|
1776
1661
|
return node;
|
|
1777
1662
|
}
|
|
1778
1663
|
function isNullCheck(expr) {
|
|
1779
1664
|
const op = expr.operatorToken.kind;
|
|
1780
|
-
if (op !==
|
|
1665
|
+
if (op !== ts27.SyntaxKind.ExclamationEqualsToken && op !== ts27.SyntaxKind.ExclamationEqualsEqualsToken) return false;
|
|
1781
1666
|
return isNullishLiteral(expr.right) || isNullishLiteral(expr.left);
|
|
1782
1667
|
}
|
|
1783
1668
|
function getNullCheckedIdentifier(expr) {
|
|
1784
|
-
if (
|
|
1785
|
-
if (
|
|
1669
|
+
if (ts27.isIdentifier(expr.left) && isNullishLiteral(expr.right)) return expr.left.text;
|
|
1670
|
+
if (ts27.isIdentifier(expr.right) && isNullishLiteral(expr.left)) return expr.right.text;
|
|
1786
1671
|
return null;
|
|
1787
1672
|
}
|
|
1788
1673
|
|
|
1789
1674
|
// src/rules/ts/no-ts-ignore.ts
|
|
1790
|
-
import * as
|
|
1675
|
+
import * as ts28 from "typescript";
|
|
1791
1676
|
var noTsIgnore = {
|
|
1792
1677
|
kind: "ts",
|
|
1793
1678
|
id: "no-ts-ignore",
|
|
1794
1679
|
severity: "error",
|
|
1795
1680
|
message: "@ts-ignore / @ts-expect-error suppresses type checking; fix the underlying type issue",
|
|
1796
1681
|
visit(node, ctx) {
|
|
1797
|
-
const ranges =
|
|
1682
|
+
const ranges = ts28.getLeadingCommentRanges(ctx.source, node.getFullStart());
|
|
1798
1683
|
if (!ranges) return;
|
|
1799
1684
|
for (const range of ranges) {
|
|
1800
1685
|
const text = ctx.source.slice(range.pos, range.end);
|
|
@@ -1806,22 +1691,22 @@ var noTsIgnore = {
|
|
|
1806
1691
|
};
|
|
1807
1692
|
|
|
1808
1693
|
// src/rules/ts/no-type-assertion.ts
|
|
1809
|
-
import * as
|
|
1694
|
+
import * as ts29 from "typescript";
|
|
1810
1695
|
var noTypeAssertion = {
|
|
1811
1696
|
kind: "ts",
|
|
1812
1697
|
id: "no-type-assertion",
|
|
1813
1698
|
severity: "error",
|
|
1814
1699
|
message: "Double type assertion (`as unknown as T`) circumvents the type system; fix the upstream type or use a type guard",
|
|
1815
1700
|
visit(node, ctx) {
|
|
1816
|
-
if (!
|
|
1817
|
-
if (!
|
|
1818
|
-
if (node.expression.type.kind !==
|
|
1701
|
+
if (!ts29.isAsExpression(node)) return;
|
|
1702
|
+
if (!ts29.isAsExpression(node.expression)) return;
|
|
1703
|
+
if (node.expression.type.kind !== ts29.SyntaxKind.UnknownKeyword) return;
|
|
1819
1704
|
ctx.report(node);
|
|
1820
1705
|
}
|
|
1821
1706
|
};
|
|
1822
1707
|
|
|
1823
1708
|
// src/rules/ts/prefer-default-param-value.ts
|
|
1824
|
-
import * as
|
|
1709
|
+
import * as ts30 from "typescript";
|
|
1825
1710
|
var preferDefaultParamValue = {
|
|
1826
1711
|
kind: "ts",
|
|
1827
1712
|
id: "prefer-default-param-value",
|
|
@@ -1831,21 +1716,21 @@ var preferDefaultParamValue = {
|
|
|
1831
1716
|
const result = getFirstFunctionStatement(node);
|
|
1832
1717
|
if (result === null) return;
|
|
1833
1718
|
const { firstStmt, fn } = result;
|
|
1834
|
-
if (!
|
|
1719
|
+
if (!ts30.isExpressionStatement(firstStmt)) return;
|
|
1835
1720
|
const expr = firstStmt.expression;
|
|
1836
|
-
if (!
|
|
1721
|
+
if (!ts30.isBinaryExpression(expr) || expr.operatorToken.kind !== ts30.SyntaxKind.EqualsToken) return;
|
|
1837
1722
|
const right = expr.right;
|
|
1838
|
-
if (!
|
|
1839
|
-
if (!
|
|
1723
|
+
if (!ts30.isBinaryExpression(right) || right.operatorToken.kind !== ts30.SyntaxKind.QuestionQuestionToken) return;
|
|
1724
|
+
if (!ts30.isIdentifier(expr.left) || !ts30.isIdentifier(right.left)) return;
|
|
1840
1725
|
if (expr.left.text !== right.left.text) return;
|
|
1841
1726
|
const paramName = expr.left.text;
|
|
1842
|
-
const isParam = fn.parameters.some((p) =>
|
|
1727
|
+
const isParam = fn.parameters.some((p) => ts30.isIdentifier(p.name) && p.name.text === paramName);
|
|
1843
1728
|
if (isParam) ctx.report(firstStmt);
|
|
1844
1729
|
}
|
|
1845
1730
|
};
|
|
1846
1731
|
|
|
1847
1732
|
// src/rules/ts/prefer-required-param-with-guard.ts
|
|
1848
|
-
import * as
|
|
1733
|
+
import * as ts31 from "typescript";
|
|
1849
1734
|
var preferRequiredParamWithGuard = {
|
|
1850
1735
|
kind: "ts",
|
|
1851
1736
|
id: "prefer-required-param-with-guard",
|
|
@@ -1855,26 +1740,26 @@ var preferRequiredParamWithGuard = {
|
|
|
1855
1740
|
const result = getFirstFunctionStatement(node);
|
|
1856
1741
|
if (result === null) return;
|
|
1857
1742
|
const { firstStmt, fn } = result;
|
|
1858
|
-
if (!
|
|
1743
|
+
if (!ts31.isIfStatement(firstStmt)) return;
|
|
1859
1744
|
const test = firstStmt.expression;
|
|
1860
1745
|
let guardedName = null;
|
|
1861
|
-
if (
|
|
1862
|
-
if (
|
|
1746
|
+
if (ts31.isPrefixUnaryExpression(test) && test.operator === ts31.SyntaxKind.ExclamationToken) {
|
|
1747
|
+
if (ts31.isIdentifier(test.operand)) guardedName = test.operand.text;
|
|
1863
1748
|
}
|
|
1864
|
-
if (
|
|
1749
|
+
if (ts31.isBinaryExpression(test)) {
|
|
1865
1750
|
const op = test.operatorToken.kind;
|
|
1866
|
-
if (op ===
|
|
1867
|
-
if (
|
|
1751
|
+
if (op === ts31.SyntaxKind.EqualsEqualsEqualsToken || op === ts31.SyntaxKind.EqualsEqualsToken) {
|
|
1752
|
+
if (ts31.isIdentifier(test.left) && ts31.isIdentifier(test.right) && test.right.text === "undefined") {
|
|
1868
1753
|
guardedName = test.left.text;
|
|
1869
1754
|
}
|
|
1870
1755
|
}
|
|
1871
1756
|
}
|
|
1872
1757
|
if (!guardedName) return;
|
|
1873
1758
|
const consequent = firstStmt.thenStatement;
|
|
1874
|
-
const isGuard =
|
|
1759
|
+
const isGuard = ts31.isReturnStatement(consequent) || ts31.isThrowStatement(consequent) || ts31.isBlock(consequent) && consequent.statements.length === 1 && consequent.statements[0] !== void 0 && (ts31.isReturnStatement(consequent.statements[0]) || ts31.isThrowStatement(consequent.statements[0]));
|
|
1875
1760
|
if (!isGuard) return;
|
|
1876
1761
|
const isOptional = fn.parameters.some(
|
|
1877
|
-
(p) =>
|
|
1762
|
+
(p) => ts31.isIdentifier(p.name) && p.name.text === guardedName && p.questionToken !== void 0
|
|
1878
1763
|
);
|
|
1879
1764
|
if (isOptional) ctx.report(firstStmt);
|
|
1880
1765
|
}
|
|
@@ -1901,7 +1786,6 @@ var allRules = [
|
|
|
1901
1786
|
preferDefaultParamValue,
|
|
1902
1787
|
preferRequiredParamWithGuard,
|
|
1903
1788
|
noInlineParamType,
|
|
1904
|
-
noModuleStateWrite,
|
|
1905
1789
|
duplicateTypeDeclaration,
|
|
1906
1790
|
duplicateFunctionDeclaration,
|
|
1907
1791
|
optionalArgAlwaysUsed,
|
|
@@ -1944,7 +1828,6 @@ var ruleMetadata = {
|
|
|
1944
1828
|
"prefer-default-param-value": { category: "interface-design", tags: ["api"] },
|
|
1945
1829
|
"prefer-required-param-with-guard": { category: "interface-design", tags: ["api"] },
|
|
1946
1830
|
"no-inline-param-type": { category: "interface-design", tags: ["api"] },
|
|
1947
|
-
"no-module-state-write": { category: "state-management", tags: ["state"] },
|
|
1948
1831
|
"duplicate-type-declaration": { category: "cross-file", tags: ["duplicate"] },
|
|
1949
1832
|
"duplicate-type-name": { category: "cross-file", tags: ["duplicate"] },
|
|
1950
1833
|
"duplicate-function-declaration": { category: "cross-file", tags: ["duplicate"] },
|
|
@@ -2021,32 +1904,36 @@ function isFailOn(value) {
|
|
|
2021
1904
|
}
|
|
2022
1905
|
|
|
2023
1906
|
// src/collect/index.ts
|
|
2024
|
-
import * as
|
|
1907
|
+
import * as ts34 from "typescript";
|
|
2025
1908
|
import { createHash as createHash2 } from "crypto";
|
|
2026
1909
|
|
|
1910
|
+
// src/collect/type-registry.ts
|
|
1911
|
+
import { existsSync } from "fs";
|
|
1912
|
+
import { dirname as dirname2, join } from "path";
|
|
1913
|
+
|
|
2027
1914
|
// src/utils/hash.ts
|
|
2028
1915
|
import { createHash } from "crypto";
|
|
2029
|
-
import * as
|
|
1916
|
+
import * as ts32 from "typescript";
|
|
2030
1917
|
function hashTypeShape(node, sourceFile) {
|
|
2031
1918
|
const normalized = normalizeTypeNode(node, sourceFile);
|
|
2032
1919
|
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
2033
1920
|
}
|
|
2034
1921
|
function normalizeTypeNode(node, sourceFile) {
|
|
2035
|
-
if (
|
|
1922
|
+
if (ts32.isTypeLiteralNode(node)) {
|
|
2036
1923
|
const normalized = node.members.map((m) => normalizeTypeNode(m, sourceFile)).sort().join(";");
|
|
2037
1924
|
return `{${normalized}}`;
|
|
2038
1925
|
}
|
|
2039
|
-
if (
|
|
1926
|
+
if (ts32.isInterfaceDeclaration(node)) {
|
|
2040
1927
|
const normalized = node.members.map((m) => normalizeTypeNode(m, sourceFile)).sort().join(";");
|
|
2041
1928
|
return `{${normalized}}`;
|
|
2042
1929
|
}
|
|
2043
|
-
if (
|
|
1930
|
+
if (ts32.isPropertySignature(node)) {
|
|
2044
1931
|
const keyName = node.name.getText(sourceFile);
|
|
2045
1932
|
const optional = node.questionToken ? "?" : "";
|
|
2046
1933
|
const type = node.type ? normalizeTypeNode(node.type, sourceFile) : "any";
|
|
2047
1934
|
return `${keyName}${optional}:${type}`;
|
|
2048
1935
|
}
|
|
2049
|
-
if (
|
|
1936
|
+
if (ts32.isTypeAliasDeclaration(node)) {
|
|
2050
1937
|
return normalizeTypeNode(node.type, sourceFile);
|
|
2051
1938
|
}
|
|
2052
1939
|
return node.getText(sourceFile).replace(/\s+/g, " ").trim();
|
|
@@ -2149,22 +2036,42 @@ var TypeRegistry = class extends BaseRegistry {
|
|
|
2149
2036
|
}
|
|
2150
2037
|
};
|
|
2151
2038
|
function getExportedNameCollisions(entries) {
|
|
2152
|
-
const
|
|
2039
|
+
const byNameAndPackage = /* @__PURE__ */ new Map();
|
|
2153
2040
|
for (const entry of entries) {
|
|
2154
2041
|
if (!entry.exported) continue;
|
|
2155
|
-
|
|
2042
|
+
const pkg = findPackageRoot(entry.file);
|
|
2043
|
+
const key = `${pkg}\0${entry.name}`;
|
|
2044
|
+
let list = byNameAndPackage.get(key);
|
|
2156
2045
|
if (list === void 0) {
|
|
2157
2046
|
list = [];
|
|
2158
|
-
|
|
2047
|
+
byNameAndPackage.set(key, list);
|
|
2159
2048
|
}
|
|
2160
2049
|
list.push(entry);
|
|
2161
2050
|
}
|
|
2162
|
-
return [...
|
|
2051
|
+
return [...byNameAndPackage.values()].filter((group) => {
|
|
2163
2052
|
if (group.length < 2) return false;
|
|
2164
2053
|
const files = new Set(group.map((e) => e.file));
|
|
2165
2054
|
return files.size > 1;
|
|
2166
2055
|
});
|
|
2167
2056
|
}
|
|
2057
|
+
var packageRootCache = /* @__PURE__ */ new Map();
|
|
2058
|
+
function findPackageRoot(filePath) {
|
|
2059
|
+
let dir = dirname2(filePath);
|
|
2060
|
+
const cached = packageRootCache.get(dir);
|
|
2061
|
+
if (cached !== void 0) return cached;
|
|
2062
|
+
const startDir = dir;
|
|
2063
|
+
const visited = [dir];
|
|
2064
|
+
while (dir !== dirname2(dir)) {
|
|
2065
|
+
if (existsSync(join(dir, "package.json"))) {
|
|
2066
|
+
for (const d of visited) packageRootCache.set(d, dir);
|
|
2067
|
+
return dir;
|
|
2068
|
+
}
|
|
2069
|
+
dir = dirname2(dir);
|
|
2070
|
+
visited.push(dir);
|
|
2071
|
+
}
|
|
2072
|
+
for (const d of visited) packageRootCache.set(d, dir);
|
|
2073
|
+
return dir;
|
|
2074
|
+
}
|
|
2168
2075
|
|
|
2169
2076
|
// src/collect/function-registry.ts
|
|
2170
2077
|
var FunctionRegistry = class extends DualHashRegistry {
|
|
@@ -2212,7 +2119,7 @@ var InlineParamTypeRegistry = class extends BaseRegistry {
|
|
|
2212
2119
|
};
|
|
2213
2120
|
|
|
2214
2121
|
// src/typecheck/walk.ts
|
|
2215
|
-
import * as
|
|
2122
|
+
import * as ts33 from "typescript";
|
|
2216
2123
|
function buildContext(rule, sourceFile, checker, source, filename, diagnostics) {
|
|
2217
2124
|
return {
|
|
2218
2125
|
filename,
|
|
@@ -2220,7 +2127,7 @@ function buildContext(rule, sourceFile, checker, source, filename, diagnostics)
|
|
|
2220
2127
|
sourceFile,
|
|
2221
2128
|
checker,
|
|
2222
2129
|
report(node, message) {
|
|
2223
|
-
const { line, character } =
|
|
2130
|
+
const { line, character } = ts33.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));
|
|
2224
2131
|
diagnostics.push({
|
|
2225
2132
|
ruleId: rule.id,
|
|
2226
2133
|
severity: rule.severity,
|
|
@@ -2231,7 +2138,7 @@ function buildContext(rule, sourceFile, checker, source, filename, diagnostics)
|
|
|
2231
2138
|
});
|
|
2232
2139
|
},
|
|
2233
2140
|
reportAtOffset(offset, message) {
|
|
2234
|
-
const { line, character } =
|
|
2141
|
+
const { line, character } = ts33.getLineAndCharacterOfPosition(sourceFile, offset);
|
|
2235
2142
|
diagnostics.push({
|
|
2236
2143
|
ruleId: rule.id,
|
|
2237
2144
|
severity: rule.severity,
|
|
@@ -2282,21 +2189,23 @@ function collectProject(program, tsRules, allowedFiles) {
|
|
|
2282
2189
|
rule.visit(node, ctx);
|
|
2283
2190
|
}
|
|
2284
2191
|
}
|
|
2285
|
-
|
|
2192
|
+
ts34.forEachChild(node, visit2);
|
|
2286
2193
|
};
|
|
2287
2194
|
var visit = visit2;
|
|
2288
2195
|
const file = sourceFile.fileName;
|
|
2289
2196
|
if (sourceFile.isDeclarationFile) continue;
|
|
2290
2197
|
if (file.includes("node_modules")) continue;
|
|
2291
|
-
|
|
2198
|
+
const isReportable = !allowedFiles || allowedFiles.has(file);
|
|
2292
2199
|
const source = sourceFile.getFullText();
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2200
|
+
if (isReportable) {
|
|
2201
|
+
const comments = collectAllComments(sourceFile);
|
|
2202
|
+
fileMap.set(file, { source, sourceFile, comments });
|
|
2203
|
+
}
|
|
2204
|
+
const ruleContexts = isReportable ? tsRules?.map((rule) => ({
|
|
2296
2205
|
rule,
|
|
2297
2206
|
ctx: buildContext(rule, sourceFile, checker, source, file, diagnostics)
|
|
2298
|
-
}));
|
|
2299
|
-
|
|
2207
|
+
})) : void 0;
|
|
2208
|
+
ts34.forEachChild(sourceFile, visit2);
|
|
2300
2209
|
}
|
|
2301
2210
|
const fileHashes = /* @__PURE__ */ new Map();
|
|
2302
2211
|
for (const [file, { source }] of fileMap) {
|
|
@@ -2312,52 +2221,52 @@ function collectProject(program, tsRules, allowedFiles) {
|
|
|
2312
2221
|
return { index: { types, functions, constants, callSites, imports, files: fileMap, fileHashes, statementSequences, inlineParamTypes }, diagnostics };
|
|
2313
2222
|
}
|
|
2314
2223
|
function hasNonPublicModifier(node) {
|
|
2315
|
-
if (!
|
|
2316
|
-
const mods =
|
|
2224
|
+
if (!ts34.canHaveModifiers(node)) return false;
|
|
2225
|
+
const mods = ts34.getModifiers(node);
|
|
2317
2226
|
if (mods === void 0) return false;
|
|
2318
2227
|
return mods.some(
|
|
2319
|
-
(m) => m.kind ===
|
|
2228
|
+
(m) => m.kind === ts34.SyntaxKind.PrivateKeyword || m.kind === ts34.SyntaxKind.ProtectedKeyword
|
|
2320
2229
|
);
|
|
2321
2230
|
}
|
|
2322
2231
|
function isExported(node) {
|
|
2323
|
-
if (!
|
|
2324
|
-
const mods =
|
|
2232
|
+
if (!ts34.canHaveModifiers(node)) return false;
|
|
2233
|
+
const mods = ts34.getModifiers(node);
|
|
2325
2234
|
if (mods === void 0) return false;
|
|
2326
|
-
return mods.some((m) => m.kind ===
|
|
2235
|
+
return mods.some((m) => m.kind === ts34.SyntaxKind.ExportKeyword);
|
|
2327
2236
|
}
|
|
2328
2237
|
function collectTypes(node, file, sourceFile, registry) {
|
|
2329
|
-
if (
|
|
2330
|
-
const line =
|
|
2238
|
+
if (ts34.isTypeAliasDeclaration(node)) {
|
|
2239
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
|
|
2331
2240
|
registry.add(node.name.text, file, line, node.type, sourceFile, isExported(node));
|
|
2332
2241
|
}
|
|
2333
|
-
if (
|
|
2334
|
-
const line =
|
|
2242
|
+
if (ts34.isInterfaceDeclaration(node)) {
|
|
2243
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
|
|
2335
2244
|
registry.add(node.name.text, file, line, node, sourceFile, isExported(node));
|
|
2336
2245
|
}
|
|
2337
2246
|
}
|
|
2338
2247
|
function isConstantValue(node) {
|
|
2339
|
-
if (
|
|
2340
|
-
if (
|
|
2341
|
-
if (
|
|
2342
|
-
if (
|
|
2343
|
-
if (
|
|
2248
|
+
if (ts34.isStringLiteral(node)) return true;
|
|
2249
|
+
if (ts34.isNumericLiteral(node)) return true;
|
|
2250
|
+
if (ts34.isNoSubstitutionTemplateLiteral(node)) return true;
|
|
2251
|
+
if (ts34.isPrefixUnaryExpression(node)) return isConstantValue(node.operand);
|
|
2252
|
+
if (ts34.isBinaryExpression(node)) return isConstantValue(node.left) && isConstantValue(node.right);
|
|
2344
2253
|
return false;
|
|
2345
2254
|
}
|
|
2346
2255
|
function collectConstants(node, file, sourceFile, registry) {
|
|
2347
|
-
if (!
|
|
2348
|
-
if (!(node.declarationList.flags &
|
|
2256
|
+
if (!ts34.isVariableStatement(node)) return;
|
|
2257
|
+
if (!(node.declarationList.flags & ts34.NodeFlags.Const)) return;
|
|
2349
2258
|
const exported = isExported(node);
|
|
2350
2259
|
for (const decl of node.declarationList.declarations) {
|
|
2351
|
-
if (!decl.initializer || !
|
|
2260
|
+
if (!decl.initializer || !ts34.isIdentifier(decl.name)) continue;
|
|
2352
2261
|
if (!isConstantValue(decl.initializer)) continue;
|
|
2353
2262
|
const valueText = decl.initializer.getText(sourceFile).replace(/\s+/g, " ").trim();
|
|
2354
2263
|
const valueHash = createHash2("sha256").update(valueText).digest("hex").slice(0, 16);
|
|
2355
|
-
const line =
|
|
2264
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1;
|
|
2356
2265
|
registry.add({ name: decl.name.text, file, line, valueHash, valueText, exported });
|
|
2357
2266
|
}
|
|
2358
2267
|
}
|
|
2359
2268
|
function buildFunctionEntry(body, parameters, sourceFile, file, lineNode, name, extra) {
|
|
2360
|
-
const line =
|
|
2269
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, lineNode.getStart(sourceFile)).line + 1;
|
|
2361
2270
|
const params = extractParams(parameters, sourceFile);
|
|
2362
2271
|
const paramNames = params.map((p) => p.name);
|
|
2363
2272
|
const hash = hashFunctionBody(body, sourceFile);
|
|
@@ -2379,17 +2288,17 @@ function buildFunctionEntry(body, parameters, sourceFile, file, lineNode, name,
|
|
|
2379
2288
|
};
|
|
2380
2289
|
}
|
|
2381
2290
|
function collectFunctions(node, file, sourceFile, checker, registry) {
|
|
2382
|
-
if (
|
|
2291
|
+
if (ts34.isFunctionDeclaration(node) && node.name && node.body) {
|
|
2383
2292
|
registry.add(buildFunctionEntry(node.body, node.parameters, sourceFile, file, node, node.name.text, {
|
|
2384
2293
|
exported: isExported(node),
|
|
2385
2294
|
symbol: checker.getSymbolAtLocation(node.name),
|
|
2386
2295
|
node
|
|
2387
2296
|
}));
|
|
2388
2297
|
}
|
|
2389
|
-
if (
|
|
2298
|
+
if (ts34.isVariableStatement(node)) {
|
|
2390
2299
|
const exported = isExported(node);
|
|
2391
2300
|
for (const decl of node.declarationList.declarations) {
|
|
2392
|
-
if (decl.initializer &&
|
|
2301
|
+
if (decl.initializer && ts34.isArrowFunction(decl.initializer) && ts34.isIdentifier(decl.name)) {
|
|
2393
2302
|
const arrow = decl.initializer;
|
|
2394
2303
|
registry.add(buildFunctionEntry(arrow.body, arrow.parameters, sourceFile, file, decl, decl.name.text, {
|
|
2395
2304
|
exported,
|
|
@@ -2397,7 +2306,7 @@ function collectFunctions(node, file, sourceFile, checker, registry) {
|
|
|
2397
2306
|
node: arrow
|
|
2398
2307
|
}));
|
|
2399
2308
|
}
|
|
2400
|
-
if (decl.initializer &&
|
|
2309
|
+
if (decl.initializer && ts34.isFunctionExpression(decl.initializer) && ts34.isIdentifier(decl.name)) {
|
|
2401
2310
|
const fn = decl.initializer;
|
|
2402
2311
|
if (fn.body) {
|
|
2403
2312
|
registry.add(buildFunctionEntry(fn.body, fn.parameters, sourceFile, file, decl, decl.name.text, {
|
|
@@ -2409,22 +2318,22 @@ function collectFunctions(node, file, sourceFile, checker, registry) {
|
|
|
2409
2318
|
}
|
|
2410
2319
|
}
|
|
2411
2320
|
}
|
|
2412
|
-
if (
|
|
2321
|
+
if (ts34.isPropertyAssignment(node) && (ts34.isIdentifier(node.name) || ts34.isStringLiteral(node.name))) {
|
|
2413
2322
|
const init = node.initializer;
|
|
2414
2323
|
const propName = node.name.text;
|
|
2415
|
-
if (
|
|
2324
|
+
if (ts34.isArrowFunction(init)) {
|
|
2416
2325
|
registry.add(buildFunctionEntry(init.body, init.parameters, sourceFile, file, node, propName, { node: init }));
|
|
2417
2326
|
}
|
|
2418
|
-
if (
|
|
2327
|
+
if (ts34.isFunctionExpression(init) && init.body) {
|
|
2419
2328
|
registry.add(buildFunctionEntry(init.body, init.parameters, sourceFile, file, node, propName, { node: init }));
|
|
2420
2329
|
}
|
|
2421
2330
|
}
|
|
2422
|
-
if (
|
|
2331
|
+
if (ts34.isArrowFunction(node) || ts34.isFunctionExpression(node)) {
|
|
2423
2332
|
const parent = node.parent;
|
|
2424
|
-
if (
|
|
2425
|
-
} else if (
|
|
2333
|
+
if (ts34.isVariableDeclaration(parent) && ts34.isIdentifier(parent.name)) {
|
|
2334
|
+
} else if (ts34.isPropertyAssignment(parent)) {
|
|
2426
2335
|
} else {
|
|
2427
|
-
const body =
|
|
2336
|
+
const body = ts34.isArrowFunction(node) ? node.body : node.body;
|
|
2428
2337
|
if (body) {
|
|
2429
2338
|
const MIN_ANON_BODY = 64;
|
|
2430
2339
|
if (bodyTextLength(body, sourceFile) >= MIN_ANON_BODY) {
|
|
@@ -2434,14 +2343,14 @@ function collectFunctions(node, file, sourceFile, checker, registry) {
|
|
|
2434
2343
|
}
|
|
2435
2344
|
}
|
|
2436
2345
|
}
|
|
2437
|
-
if (
|
|
2346
|
+
if (ts34.isMethodDeclaration(node) && node.body && ts34.isIdentifier(node.name)) {
|
|
2438
2347
|
const parent = node.parent;
|
|
2439
|
-
if (
|
|
2348
|
+
if (ts34.isClassDeclaration(parent)) {
|
|
2440
2349
|
if (hasNonPublicModifier(node)) return;
|
|
2441
2350
|
const className = parent.name ? parent.name.text : "<anonymous>";
|
|
2442
2351
|
const name = `${className}.${node.name.text}`;
|
|
2443
2352
|
const implementsInterface = parent.heritageClauses?.some(
|
|
2444
|
-
(c) => c.token ===
|
|
2353
|
+
(c) => c.token === ts34.SyntaxKind.ImplementsKeyword
|
|
2445
2354
|
) ?? false;
|
|
2446
2355
|
registry.add(buildFunctionEntry(node.body, node.parameters, sourceFile, file, node, name, {
|
|
2447
2356
|
exported: isExported(parent),
|
|
@@ -2464,16 +2373,16 @@ function extractParams(parameters, sourceFile) {
|
|
|
2464
2373
|
}
|
|
2465
2374
|
function deriveAnonymousName(node, sourceFile) {
|
|
2466
2375
|
const parent = node.parent;
|
|
2467
|
-
const line =
|
|
2468
|
-
if (
|
|
2376
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
|
|
2377
|
+
if (ts34.isCallExpression(parent)) {
|
|
2469
2378
|
const grandparent = parent.parent;
|
|
2470
|
-
if (
|
|
2379
|
+
if (ts34.isPropertyAssignment(grandparent) && ts34.isIdentifier(grandparent.name)) {
|
|
2471
2380
|
return grandparent.name.text;
|
|
2472
2381
|
}
|
|
2473
2382
|
let calleeName = null;
|
|
2474
|
-
if (
|
|
2383
|
+
if (ts34.isIdentifier(parent.expression)) {
|
|
2475
2384
|
calleeName = parent.expression.text;
|
|
2476
|
-
} else if (
|
|
2385
|
+
} else if (ts34.isPropertyAccessExpression(parent.expression)) {
|
|
2477
2386
|
calleeName = parent.expression.name.text;
|
|
2478
2387
|
}
|
|
2479
2388
|
if (calleeName) {
|
|
@@ -2484,7 +2393,7 @@ function deriveAnonymousName(node, sourceFile) {
|
|
|
2484
2393
|
return `<anonymous>:${line}`;
|
|
2485
2394
|
}
|
|
2486
2395
|
function collectImports(node, file, imports) {
|
|
2487
|
-
if (
|
|
2396
|
+
if (ts34.isImportDeclaration(node) && ts34.isStringLiteral(node.moduleSpecifier)) {
|
|
2488
2397
|
const moduleSource = node.moduleSpecifier.text;
|
|
2489
2398
|
const clause = node.importClause;
|
|
2490
2399
|
if (!clause) return;
|
|
@@ -2496,7 +2405,7 @@ function collectImports(node, file, imports) {
|
|
|
2496
2405
|
source: moduleSource
|
|
2497
2406
|
});
|
|
2498
2407
|
}
|
|
2499
|
-
if (clause.namedBindings &&
|
|
2408
|
+
if (clause.namedBindings && ts34.isNamedImports(clause.namedBindings)) {
|
|
2500
2409
|
for (const el of clause.namedBindings.elements) {
|
|
2501
2410
|
imports.push({
|
|
2502
2411
|
file,
|
|
@@ -2507,9 +2416,9 @@ function collectImports(node, file, imports) {
|
|
|
2507
2416
|
}
|
|
2508
2417
|
}
|
|
2509
2418
|
}
|
|
2510
|
-
if (
|
|
2419
|
+
if (ts34.isExportDeclaration(node) && node.moduleSpecifier && ts34.isStringLiteral(node.moduleSpecifier)) {
|
|
2511
2420
|
const moduleSource = node.moduleSpecifier.text;
|
|
2512
|
-
if (node.exportClause &&
|
|
2421
|
+
if (node.exportClause && ts34.isNamedExports(node.exportClause)) {
|
|
2513
2422
|
for (const el of node.exportClause.elements) {
|
|
2514
2423
|
imports.push({
|
|
2515
2424
|
file,
|
|
@@ -2522,7 +2431,7 @@ function collectImports(node, file, imports) {
|
|
|
2522
2431
|
}
|
|
2523
2432
|
}
|
|
2524
2433
|
function collectStatementSequences(node, file, sourceFile, registry) {
|
|
2525
|
-
if (!
|
|
2434
|
+
if (!ts34.isBlock(node)) return;
|
|
2526
2435
|
const stmts = node.statements;
|
|
2527
2436
|
const n = stmts.length;
|
|
2528
2437
|
if (n < 3) return;
|
|
@@ -2539,32 +2448,32 @@ function collectStatementSequences(node, file, sourceFile, registry) {
|
|
|
2539
2448
|
const firstStmt = window[0];
|
|
2540
2449
|
const lastStmt = window[window.length - 1];
|
|
2541
2450
|
if (firstStmt === void 0 || lastStmt === void 0) continue;
|
|
2542
|
-
const line =
|
|
2543
|
-
const endLine =
|
|
2451
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, firstStmt.getStart(sourceFile)).line + 1;
|
|
2452
|
+
const endLine = ts34.getLineAndCharacterOfPosition(sourceFile, lastStmt.getEnd()).line + 1;
|
|
2544
2453
|
registry.add({ file, line, endLine, hash, normalizedHash, statementCount: size, normalizedBodyLength: normalized.length });
|
|
2545
2454
|
}
|
|
2546
2455
|
}
|
|
2547
2456
|
}
|
|
2548
2457
|
function collectInlineParamTypes(node, file, sourceFile, registry) {
|
|
2549
2458
|
if (!isInlineParamType(node)) return;
|
|
2550
|
-
const line =
|
|
2459
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
|
|
2551
2460
|
registry.add(file, line, node, sourceFile);
|
|
2552
2461
|
}
|
|
2553
2462
|
function collectCallSites(node, file, sourceFile, checker, sites) {
|
|
2554
|
-
if (!
|
|
2463
|
+
if (!ts34.isCallExpression(node)) return;
|
|
2555
2464
|
let calleeName = null;
|
|
2556
|
-
if (
|
|
2465
|
+
if (ts34.isIdentifier(node.expression)) {
|
|
2557
2466
|
calleeName = node.expression.text;
|
|
2558
|
-
} else if (
|
|
2467
|
+
} else if (ts34.isPropertyAccessExpression(node.expression)) {
|
|
2559
2468
|
calleeName = node.expression.name.text;
|
|
2560
2469
|
}
|
|
2561
2470
|
if (calleeName) {
|
|
2562
|
-
const line =
|
|
2471
|
+
const line = ts34.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;
|
|
2563
2472
|
let symbol;
|
|
2564
2473
|
let resolvedDeclaration;
|
|
2565
2474
|
try {
|
|
2566
2475
|
symbol = checker.getSymbolAtLocation(node.expression);
|
|
2567
|
-
if (symbol && symbol.flags &
|
|
2476
|
+
if (symbol && symbol.flags & ts34.SymbolFlags.Alias) {
|
|
2568
2477
|
symbol = checker.getAliasedSymbol(symbol);
|
|
2569
2478
|
}
|
|
2570
2479
|
const signature = checker.getResolvedSignature(node);
|
|
@@ -2591,7 +2500,7 @@ function collectAllComments(sourceFile) {
|
|
|
2591
2500
|
for (const r of ranges) {
|
|
2592
2501
|
if (seen.has(r.pos)) continue;
|
|
2593
2502
|
seen.add(r.pos);
|
|
2594
|
-
const isLine = r.kind ===
|
|
2503
|
+
const isLine = r.kind === ts34.SyntaxKind.SingleLineCommentTrivia;
|
|
2595
2504
|
comments.push({
|
|
2596
2505
|
type: isLine ? "Line" : "Block",
|
|
2597
2506
|
value: source.slice(r.pos + 2, isLine ? r.end : r.end - 2),
|
|
@@ -2601,53 +2510,63 @@ function collectAllComments(sourceFile) {
|
|
|
2601
2510
|
}
|
|
2602
2511
|
}
|
|
2603
2512
|
function visit(node) {
|
|
2604
|
-
addRanges(
|
|
2605
|
-
addRanges(
|
|
2606
|
-
|
|
2513
|
+
addRanges(ts34.getLeadingCommentRanges(source, node.getFullStart()));
|
|
2514
|
+
addRanges(ts34.getTrailingCommentRanges(source, node.getEnd()));
|
|
2515
|
+
ts34.forEachChild(node, visit);
|
|
2607
2516
|
}
|
|
2608
2517
|
visit(sourceFile);
|
|
2609
2518
|
return comments;
|
|
2610
2519
|
}
|
|
2611
2520
|
|
|
2612
2521
|
// src/typecheck/program.ts
|
|
2613
|
-
import * as
|
|
2614
|
-
import { dirname as
|
|
2522
|
+
import * as ts35 from "typescript";
|
|
2523
|
+
import { dirname as dirname3 } from "path";
|
|
2615
2524
|
function createProgramFromFiles(files) {
|
|
2616
2525
|
let configPath;
|
|
2617
2526
|
if (files.length > 0) {
|
|
2618
|
-
configPath =
|
|
2527
|
+
configPath = ts35.findConfigFile(dirname3(files[0]), ts35.sys.fileExists, "tsconfig.json");
|
|
2619
2528
|
}
|
|
2620
2529
|
if (configPath) {
|
|
2621
|
-
const configFile =
|
|
2622
|
-
const parsed =
|
|
2623
|
-
return
|
|
2530
|
+
const configFile = ts35.readConfigFile(configPath, ts35.sys.readFile);
|
|
2531
|
+
const parsed = ts35.parseJsonConfigFileContent(configFile.config, ts35.sys, dirname3(configPath));
|
|
2532
|
+
return ts35.createProgram({
|
|
2624
2533
|
rootNames: files,
|
|
2625
2534
|
options: { ...parsed.options, skipLibCheck: true }
|
|
2626
2535
|
});
|
|
2627
2536
|
}
|
|
2628
|
-
return
|
|
2537
|
+
return ts35.createProgram({
|
|
2629
2538
|
rootNames: files,
|
|
2630
2539
|
options: {
|
|
2631
|
-
target:
|
|
2632
|
-
module:
|
|
2633
|
-
moduleResolution:
|
|
2540
|
+
target: ts35.ScriptTarget.ESNext,
|
|
2541
|
+
module: ts35.ModuleKind.ESNext,
|
|
2542
|
+
moduleResolution: ts35.ModuleResolutionKind.Bundler,
|
|
2634
2543
|
strict: true,
|
|
2635
2544
|
skipLibCheck: true,
|
|
2636
2545
|
noEmit: true
|
|
2637
2546
|
}
|
|
2638
2547
|
});
|
|
2639
2548
|
}
|
|
2549
|
+
function resolveProjectFiles(scanFiles) {
|
|
2550
|
+
if (scanFiles.length === 0) return [];
|
|
2551
|
+
const configPath = ts35.findConfigFile(dirname3(scanFiles[0]), ts35.sys.fileExists, "tsconfig.json");
|
|
2552
|
+
if (!configPath) return scanFiles;
|
|
2553
|
+
const configFile = ts35.readConfigFile(configPath, ts35.sys.readFile);
|
|
2554
|
+
const parsed = ts35.parseJsonConfigFileContent(configFile.config, ts35.sys, dirname3(configPath));
|
|
2555
|
+
return [.../* @__PURE__ */ new Set([...scanFiles, ...parsed.fileNames])];
|
|
2556
|
+
}
|
|
2640
2557
|
|
|
2641
2558
|
// src/scan/analyze.ts
|
|
2642
2559
|
function analyzeFiles(files, rules) {
|
|
2643
2560
|
const tsRules = rules.filter(isTSRule);
|
|
2644
2561
|
const crossFileRules = rules.filter((r) => !isTSRule(r));
|
|
2645
|
-
const
|
|
2562
|
+
const projectFiles = resolveProjectFiles(files);
|
|
2563
|
+
const program = projectFiles.length > 0 ? createProgramFromFiles(projectFiles) : null;
|
|
2646
2564
|
if (!program) return [];
|
|
2647
2565
|
const allowedFiles = new Set(files);
|
|
2648
2566
|
const { index, diagnostics } = collectProject(program, tsRules, allowedFiles);
|
|
2649
2567
|
for (const rule of crossFileRules) {
|
|
2650
|
-
|
|
2568
|
+
const ruleDiagnostics = rule.analyze(index);
|
|
2569
|
+
diagnostics.push(...ruleDiagnostics.filter((d) => allowedFiles.has(d.file)));
|
|
2651
2570
|
}
|
|
2652
2571
|
return finalizeDiagnostics(diagnostics, index.files);
|
|
2653
2572
|
}
|
|
@@ -2791,7 +2710,7 @@ function lineAt(source, offset) {
|
|
|
2791
2710
|
// src/scan/discover.ts
|
|
2792
2711
|
import fg from "fast-glob";
|
|
2793
2712
|
import ignore from "ignore";
|
|
2794
|
-
import { existsSync, readFileSync } from "fs";
|
|
2713
|
+
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
2795
2714
|
import { relative, resolve as resolve2, sep } from "path";
|
|
2796
2715
|
async function discoverFiles(config) {
|
|
2797
2716
|
const globs = expandGlobs(config.paths);
|
|
@@ -2814,7 +2733,7 @@ function expandGlobs(paths) {
|
|
|
2814
2733
|
}
|
|
2815
2734
|
function applyGitIgnore(files) {
|
|
2816
2735
|
const gitIgnorePath = resolve2(process.cwd(), ".gitignore");
|
|
2817
|
-
if (!
|
|
2736
|
+
if (!existsSync2(gitIgnorePath)) return files;
|
|
2818
2737
|
const matcher = ignore().add(readFileSync(gitIgnorePath, "utf8"));
|
|
2819
2738
|
return files.filter((file) => {
|
|
2820
2739
|
const rel = relative(process.cwd(), file);
|
|
@@ -2929,4 +2848,4 @@ export {
|
|
|
2929
2848
|
executeScan,
|
|
2930
2849
|
scan
|
|
2931
2850
|
};
|
|
2932
|
-
//# sourceMappingURL=chunk-
|
|
2851
|
+
//# sourceMappingURL=chunk-WUH4JLMB.js.map
|