technical-debt-radar 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +130 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16578,6 +16578,31 @@ var require_reliability_detector = __commonJS({
|
|
|
16578
16578
|
});
|
|
16579
16579
|
}
|
|
16580
16580
|
var LOGGING_PATTERNS = /\b(logger|console\.(?:error|warn)|report|sentry|bugsnag|datadog|newrelic|winston|pino|bunyan)\b/i;
|
|
16581
|
+
var SAFE_FALLBACK_PATTERN = /^return\s+(true|false|null|undefined|void\s+0|0|\[\s*\]|\{\s*\})\s*;?$/;
|
|
16582
|
+
function isSafeFallbackReturn(block) {
|
|
16583
|
+
const statements = block.getStatements();
|
|
16584
|
+
if (statements.length === 0)
|
|
16585
|
+
return false;
|
|
16586
|
+
let returnStatement;
|
|
16587
|
+
let hasOtherSideEffects = false;
|
|
16588
|
+
for (const stmt of statements) {
|
|
16589
|
+
if (ts_morph_1.Node.isReturnStatement(stmt)) {
|
|
16590
|
+
returnStatement = stmt;
|
|
16591
|
+
} else if (ts_morph_1.Node.isVariableStatement(stmt)) {
|
|
16592
|
+
continue;
|
|
16593
|
+
} else {
|
|
16594
|
+
hasOtherSideEffects = true;
|
|
16595
|
+
}
|
|
16596
|
+
}
|
|
16597
|
+
if (!returnStatement || hasOtherSideEffects)
|
|
16598
|
+
return false;
|
|
16599
|
+
const returnText = returnStatement.getText().trim();
|
|
16600
|
+
if (SAFE_FALLBACK_PATTERN.test(returnText))
|
|
16601
|
+
return true;
|
|
16602
|
+
if (statements.length === 1 && returnText.startsWith("return "))
|
|
16603
|
+
return true;
|
|
16604
|
+
return false;
|
|
16605
|
+
}
|
|
16581
16606
|
function detectMissingErrorLogging(sourceFile, filePath, fns, policy, violations) {
|
|
16582
16607
|
sourceFile.forEachDescendant((node) => {
|
|
16583
16608
|
if (!ts_morph_1.Node.isCatchClause(node))
|
|
@@ -16596,6 +16621,8 @@ var require_reliability_detector = __commonJS({
|
|
|
16596
16621
|
});
|
|
16597
16622
|
if (hasThrow)
|
|
16598
16623
|
return;
|
|
16624
|
+
if (isSafeFallbackReturn(block))
|
|
16625
|
+
return;
|
|
16599
16626
|
const hasIncrement = blockText.includes("++") || blockText.includes("+=");
|
|
16600
16627
|
const hasPropertyMethodCall = /this\.\w+/.test(blockText);
|
|
16601
16628
|
const hasAwaitOrDelay = /\bawait\b|\bsetTimeout\b|\bsleep\b|\bdelay\b/i.test(blockText);
|
|
@@ -17708,13 +17735,15 @@ var require_dead_code_detector = __commonJS({
|
|
|
17708
17735
|
excludeTypes: true,
|
|
17709
17736
|
severity: "info"
|
|
17710
17737
|
};
|
|
17711
|
-
async function detectDeadCode(input, config = {}) {
|
|
17738
|
+
async function detectDeadCode(input, config = {}, projectRoot) {
|
|
17712
17739
|
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
17713
17740
|
if (!cfg.enabled) {
|
|
17714
17741
|
return emptyResult();
|
|
17715
17742
|
}
|
|
17716
17743
|
const project = createProject(input);
|
|
17717
17744
|
const sourceFiles = /* @__PURE__ */ new Map();
|
|
17745
|
+
const effectiveRoot = projectRoot ?? input.projectRoot;
|
|
17746
|
+
const { aliases } = parsePathAliases(input, effectiveRoot);
|
|
17718
17747
|
for (const sf of project.getSourceFiles()) {
|
|
17719
17748
|
const filePath = normalizeFilePath(sf.getFilePath());
|
|
17720
17749
|
sourceFiles.set(filePath, sf);
|
|
@@ -17730,7 +17759,7 @@ var require_dead_code_detector = __commonJS({
|
|
|
17730
17759
|
}
|
|
17731
17760
|
const importRefs = /* @__PURE__ */ new Map();
|
|
17732
17761
|
for (const [filePath, sf] of sourceFiles) {
|
|
17733
|
-
const refs = extractImportReferences(sf, filePath, project);
|
|
17762
|
+
const refs = extractImportReferences(sf, filePath, project, aliases);
|
|
17734
17763
|
if (refs.size > 0) {
|
|
17735
17764
|
importRefs.set(filePath, refs);
|
|
17736
17765
|
}
|
|
@@ -17772,6 +17801,9 @@ var require_dead_code_detector = __commonJS({
|
|
|
17772
17801
|
continue;
|
|
17773
17802
|
if (cfg.excludeTypes && isTypeExport(exp.type))
|
|
17774
17803
|
continue;
|
|
17804
|
+
const sf = sourceFiles.get(filePath);
|
|
17805
|
+
if (sf && exp.type === "class" && isNestJSDIRegistered(sf, exp.name))
|
|
17806
|
+
continue;
|
|
17775
17807
|
unusedExports.push({
|
|
17776
17808
|
export: exp,
|
|
17777
17809
|
file: filePath,
|
|
@@ -17849,7 +17881,7 @@ var require_dead_code_detector = __commonJS({
|
|
|
17849
17881
|
}
|
|
17850
17882
|
return exports3;
|
|
17851
17883
|
}
|
|
17852
|
-
function extractImportReferences(sourceFile, filePath, project) {
|
|
17884
|
+
function extractImportReferences(sourceFile, filePath, project, aliases = []) {
|
|
17853
17885
|
const refs = /* @__PURE__ */ new Map();
|
|
17854
17886
|
function addRef(resolvedFile, name) {
|
|
17855
17887
|
if (!refs.has(resolvedFile)) {
|
|
@@ -17859,7 +17891,7 @@ var require_dead_code_detector = __commonJS({
|
|
|
17859
17891
|
}
|
|
17860
17892
|
for (const importDecl of sourceFile.getImportDeclarations()) {
|
|
17861
17893
|
const specifier = importDecl.getModuleSpecifierValue();
|
|
17862
|
-
const resolved = resolveModuleSpecifier(filePath, specifier, project);
|
|
17894
|
+
const resolved = resolveModuleSpecifier(filePath, specifier, project, aliases);
|
|
17863
17895
|
if (!resolved)
|
|
17864
17896
|
continue;
|
|
17865
17897
|
const defaultImport = importDecl.getDefaultImport();
|
|
@@ -17879,7 +17911,7 @@ var require_dead_code_detector = __commonJS({
|
|
|
17879
17911
|
const specifier = exportDecl.getModuleSpecifierValue();
|
|
17880
17912
|
if (!specifier)
|
|
17881
17913
|
continue;
|
|
17882
|
-
const resolved = resolveModuleSpecifier(filePath, specifier, project);
|
|
17914
|
+
const resolved = resolveModuleSpecifier(filePath, specifier, project, aliases);
|
|
17883
17915
|
if (!resolved)
|
|
17884
17916
|
continue;
|
|
17885
17917
|
if (exportDecl.isNamespaceExport()) {
|
|
@@ -17900,9 +17932,7 @@ var require_dead_code_detector = __commonJS({
|
|
|
17900
17932
|
if (args.length === 0 || !ts_morph_1.Node.isStringLiteral(args[0]))
|
|
17901
17933
|
return;
|
|
17902
17934
|
const modulePath = args[0].getLiteralValue();
|
|
17903
|
-
|
|
17904
|
-
return;
|
|
17905
|
-
const resolved = resolveModuleSpecifier(filePath, modulePath, project);
|
|
17935
|
+
const resolved = resolveModuleSpecifier(filePath, modulePath, project, aliases);
|
|
17906
17936
|
if (!resolved)
|
|
17907
17937
|
return;
|
|
17908
17938
|
const parent = node.getParent();
|
|
@@ -17955,6 +17985,88 @@ var require_dead_code_detector = __commonJS({
|
|
|
17955
17985
|
function isTypeExport(type) {
|
|
17956
17986
|
return type === "type" || type === "interface" || type === "enum";
|
|
17957
17987
|
}
|
|
17988
|
+
function parsePathAliases(input, projectRoot) {
|
|
17989
|
+
let tsconfigContent;
|
|
17990
|
+
const tsconfigFile = input.changedFiles.find((f) => f.status !== "deleted" && /tsconfig(?:\.build)?\.json$/.test(f.path));
|
|
17991
|
+
if (tsconfigFile) {
|
|
17992
|
+
tsconfigContent = tsconfigFile.content;
|
|
17993
|
+
}
|
|
17994
|
+
const root = projectRoot ?? input.projectRoot;
|
|
17995
|
+
if (!tsconfigContent && root) {
|
|
17996
|
+
try {
|
|
17997
|
+
const fs9 = require("fs");
|
|
17998
|
+
for (const name of ["tsconfig.json", "tsconfig.build.json"]) {
|
|
17999
|
+
const tsconfigPath = path9.join(root, name);
|
|
18000
|
+
if (fs9.existsSync(tsconfigPath)) {
|
|
18001
|
+
tsconfigContent = fs9.readFileSync(tsconfigPath, "utf-8");
|
|
18002
|
+
break;
|
|
18003
|
+
}
|
|
18004
|
+
}
|
|
18005
|
+
} catch {
|
|
18006
|
+
}
|
|
18007
|
+
}
|
|
18008
|
+
if (!tsconfigContent) {
|
|
18009
|
+
return { aliases: [], baseUrl: "." };
|
|
18010
|
+
}
|
|
18011
|
+
try {
|
|
18012
|
+
const stripped = tsconfigContent.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/,\s*([}\]])/g, "$1");
|
|
18013
|
+
const tsconfig = JSON.parse(stripped);
|
|
18014
|
+
const compilerOptions = tsconfig.compilerOptions ?? {};
|
|
18015
|
+
const rawBaseUrl = (compilerOptions.baseUrl ?? ".").replace(/\/+$/, "");
|
|
18016
|
+
const baseUrl = rawBaseUrl === "" ? "." : rawBaseUrl;
|
|
18017
|
+
const paths = compilerOptions.paths ?? {};
|
|
18018
|
+
const aliases = [];
|
|
18019
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
18020
|
+
const prefix = pattern.replace(/\/?\*$/, "");
|
|
18021
|
+
const resolvedTargets = targets.map((t) => {
|
|
18022
|
+
const target = t.replace(/\/?\*$/, "");
|
|
18023
|
+
if (baseUrl !== "." && !target.startsWith("/")) {
|
|
18024
|
+
return posixJoinAndNormalize(baseUrl, target);
|
|
18025
|
+
}
|
|
18026
|
+
return target;
|
|
18027
|
+
});
|
|
18028
|
+
aliases.push({ prefix, targets: resolvedTargets });
|
|
18029
|
+
}
|
|
18030
|
+
return { aliases, baseUrl };
|
|
18031
|
+
} catch {
|
|
18032
|
+
return { aliases: [], baseUrl: "." };
|
|
18033
|
+
}
|
|
18034
|
+
}
|
|
18035
|
+
function resolvePathAlias(specifier, aliases, project) {
|
|
18036
|
+
for (const alias of aliases) {
|
|
18037
|
+
if (alias.prefix === "" || specifier === alias.prefix || specifier.startsWith(alias.prefix + "/")) {
|
|
18038
|
+
const remainder = alias.prefix === "" ? specifier : specifier.slice(alias.prefix.length).replace(/^\//, "");
|
|
18039
|
+
for (const target of alias.targets) {
|
|
18040
|
+
const candidate = remainder ? target ? target + "/" + remainder : remainder : target;
|
|
18041
|
+
const resolved = resolveInProject(candidate, project);
|
|
18042
|
+
if (resolved)
|
|
18043
|
+
return resolved;
|
|
18044
|
+
}
|
|
18045
|
+
}
|
|
18046
|
+
}
|
|
18047
|
+
return void 0;
|
|
18048
|
+
}
|
|
18049
|
+
function isNestJSDIRegistered(sourceFile, exportName) {
|
|
18050
|
+
for (const cls of sourceFile.getClasses()) {
|
|
18051
|
+
const className = cls.getName();
|
|
18052
|
+
if (className !== exportName)
|
|
18053
|
+
continue;
|
|
18054
|
+
const decorators = cls.getDecorators();
|
|
18055
|
+
for (const dec of decorators) {
|
|
18056
|
+
const decName = dec.getName();
|
|
18057
|
+
if (decName === "Injectable" || decName === "Controller" || decName === "Guard" || decName === "Resolver" || decName === "Gateway") {
|
|
18058
|
+
return true;
|
|
18059
|
+
}
|
|
18060
|
+
}
|
|
18061
|
+
const extendsClause = cls.getExtends();
|
|
18062
|
+
if (extendsClause) {
|
|
18063
|
+
const extendsText = extendsClause.getText();
|
|
18064
|
+
if (extendsText.includes("PassportStrategy"))
|
|
18065
|
+
return true;
|
|
18066
|
+
}
|
|
18067
|
+
}
|
|
18068
|
+
return false;
|
|
18069
|
+
}
|
|
17958
18070
|
function createProject(input) {
|
|
17959
18071
|
const project = new ts_morph_1.Project({
|
|
17960
18072
|
useInMemoryFileSystem: true,
|
|
@@ -17977,13 +18089,16 @@ var require_dead_code_detector = __commonJS({
|
|
|
17977
18089
|
const normalized = filePath.replace(/\\/g, "/");
|
|
17978
18090
|
return normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
17979
18091
|
}
|
|
17980
|
-
function resolveModuleSpecifier(sourcePath, specifier, project) {
|
|
17981
|
-
if (
|
|
17982
|
-
|
|
18092
|
+
function resolveModuleSpecifier(sourcePath, specifier, project, aliases = []) {
|
|
18093
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
18094
|
+
const sourceDir = posixDirname(sourcePath);
|
|
18095
|
+
const resolved = posixJoinAndNormalize(sourceDir, specifier);
|
|
18096
|
+
return resolveInProject(resolved, project) ?? resolved;
|
|
17983
18097
|
}
|
|
17984
|
-
|
|
17985
|
-
|
|
17986
|
-
|
|
18098
|
+
if (aliases.length > 0) {
|
|
18099
|
+
return resolvePathAlias(specifier, aliases, project);
|
|
18100
|
+
}
|
|
18101
|
+
return void 0;
|
|
17987
18102
|
}
|
|
17988
18103
|
function resolveInProject(resolved, project) {
|
|
17989
18104
|
const lookup = "/" + resolved;
|
|
@@ -18429,7 +18544,7 @@ var require_orchestrator = __commonJS({
|
|
|
18429
18544
|
(0, reliability_detector_1.detectReliabilityIssues)(filteredInput, policy),
|
|
18430
18545
|
(0, duplication_detector_1.detectDuplication)(filteredInput),
|
|
18431
18546
|
projectRoot ? (0, missing_tests_detector_1.detectMissingTests)(filteredInput, projectRoot) : Promise.resolve(null),
|
|
18432
|
-
(0, dead_code_detector_1.detectDeadCode)(filteredInput),
|
|
18547
|
+
(0, dead_code_detector_1.detectDeadCode)(filteredInput, {}, projectRoot),
|
|
18433
18548
|
projectRoot ? Promise.resolve((0, coverage_delta_detector_1.detectCoverageDelta)(projectRoot)) : Promise.resolve(null)
|
|
18434
18549
|
]);
|
|
18435
18550
|
const runtimeViolations = runtimeResult.violations;
|