technical-debt-radar 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +111 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -90,7 +90,8 @@ var require_constants = __commonJS({
|
|
|
90
90
|
MISSING_NULL_GUARD: "missing-null-guard",
|
|
91
91
|
MISSING_DTO_VALIDATION: "missing-dto-validation",
|
|
92
92
|
UNGUARDED_ROUTE_TODO: "unguarded-route-todo",
|
|
93
|
-
ERROR_LOGGED_AS_INFO: "error-logged-as-info"
|
|
93
|
+
ERROR_LOGGED_AS_INFO: "error-logged-as-info",
|
|
94
|
+
CATCH_SWALLOWS_CONTEXT: "catch-block-swallows-error-context"
|
|
94
95
|
};
|
|
95
96
|
exports2.PERFORMANCE_RULES = {
|
|
96
97
|
UNBOUNDED_FIND_MANY: "unbounded-find-many",
|
|
@@ -110,6 +111,7 @@ var require_constants = __commonJS({
|
|
|
110
111
|
CODE_DUPLICATION: "code-duplication",
|
|
111
112
|
MISSING_TEST_FILE: "missing-test-file",
|
|
112
113
|
UNUSED_EXPORT: "unused-export",
|
|
114
|
+
DEBUG_CONSOLE_LOG: "debug-console-log-in-production",
|
|
113
115
|
LOW_TEST_COVERAGE: "low-test-coverage",
|
|
114
116
|
LOW_BRANCH_COVERAGE: "low-branch-coverage",
|
|
115
117
|
COVERAGE_DROP: "coverage-drop"
|
|
@@ -118,7 +120,9 @@ var require_constants = __commonJS({
|
|
|
118
120
|
LAYER_VIOLATION: "layer-boundary-violation",
|
|
119
121
|
CIRCULAR_DEPENDENCY: "circular-dependency",
|
|
120
122
|
MODULE_BOUNDARY: "module-boundary-violation",
|
|
121
|
-
FORBIDDEN_FRAMEWORK: "forbidden-framework-in-layer"
|
|
123
|
+
FORBIDDEN_FRAMEWORK: "forbidden-framework-in-layer",
|
|
124
|
+
PROVIDER_OUTSIDE_HOME: "provider-declared-outside-home-module",
|
|
125
|
+
SCHEMA_MULTI_MODULE: "schema-registered-in-multiple-modules"
|
|
122
126
|
};
|
|
123
127
|
exports2.CROSS_FILE_RULES = {
|
|
124
128
|
INDIRECT_SYNC_FS: "indirect-sync-fs",
|
|
@@ -12763,12 +12767,15 @@ var require_import_graph = __commonJS({
|
|
|
12763
12767
|
return specifiers;
|
|
12764
12768
|
}
|
|
12765
12769
|
function resolveModuleSpecifier(sourcePath, specifier, project) {
|
|
12766
|
-
if (
|
|
12767
|
-
|
|
12770
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
12771
|
+
const sourceDir = posixDirname(sourcePath);
|
|
12772
|
+
const resolved = posixJoinAndNormalize(sourceDir, specifier);
|
|
12773
|
+
return resolveInProject(resolved, project) ?? resolved;
|
|
12768
12774
|
}
|
|
12769
|
-
const
|
|
12770
|
-
|
|
12771
|
-
|
|
12775
|
+
const directResolved = resolveInProject(specifier, project);
|
|
12776
|
+
if (directResolved)
|
|
12777
|
+
return directResolved;
|
|
12778
|
+
return void 0;
|
|
12772
12779
|
}
|
|
12773
12780
|
function resolveInProject(resolved, project) {
|
|
12774
12781
|
const lookup = "/" + resolved;
|
|
@@ -12787,6 +12794,16 @@ var require_import_graph = __commonJS({
|
|
|
12787
12794
|
return indexPath;
|
|
12788
12795
|
}
|
|
12789
12796
|
}
|
|
12797
|
+
const suffix = "/" + resolved;
|
|
12798
|
+
for (const sf of project.getSourceFiles()) {
|
|
12799
|
+
const fp = sf.getFilePath();
|
|
12800
|
+
if (fp.endsWith(suffix))
|
|
12801
|
+
return normalizeFilePath(fp);
|
|
12802
|
+
for (const ext of extensions) {
|
|
12803
|
+
if (fp.endsWith(suffix + ext))
|
|
12804
|
+
return normalizeFilePath(fp);
|
|
12805
|
+
}
|
|
12806
|
+
}
|
|
12790
12807
|
return void 0;
|
|
12791
12808
|
}
|
|
12792
12809
|
function buildEdge(source, target, importType, policy) {
|
|
@@ -12877,12 +12894,22 @@ var require_boundary_checker = __commonJS({
|
|
|
12877
12894
|
return /nestjs/i.test(policy.stack.framework);
|
|
12878
12895
|
}
|
|
12879
12896
|
var suffixPattern = (suffix) => new RegExp(`\\.${suffix}(\\.(ts|tsx|js|jsx))?$`);
|
|
12880
|
-
var NESTJS_SHARED_INFRA_FOLDERS = ["guards", "decorators", "interceptors", "pipes", "filters", "middleware", "shared"];
|
|
12897
|
+
var NESTJS_SHARED_INFRA_FOLDERS = ["guards", "decorators", "interceptors", "pipes", "filters", "middleware", "shared", "strategies", "common", "config"];
|
|
12881
12898
|
function isNestJSExemptImport(sourcePath, targetPath, customSharedFolders = []) {
|
|
12882
12899
|
const srcBase = sourcePath.replace(/\\/g, "/");
|
|
12883
12900
|
const tgtBase = targetPath.replace(/\\/g, "/");
|
|
12884
12901
|
if (suffixPattern("module").test(srcBase))
|
|
12885
12902
|
return true;
|
|
12903
|
+
if (suffixPattern("strategy").test(srcBase))
|
|
12904
|
+
return true;
|
|
12905
|
+
if (suffixPattern("guard").test(srcBase))
|
|
12906
|
+
return true;
|
|
12907
|
+
if (suffixPattern("interceptor").test(srcBase))
|
|
12908
|
+
return true;
|
|
12909
|
+
if (suffixPattern("pipe").test(srcBase))
|
|
12910
|
+
return true;
|
|
12911
|
+
if (suffixPattern("filter").test(srcBase))
|
|
12912
|
+
return true;
|
|
12886
12913
|
if (suffixPattern("module").test(tgtBase))
|
|
12887
12914
|
return true;
|
|
12888
12915
|
if (suffixPattern("entity").test(tgtBase))
|
|
@@ -12899,6 +12926,8 @@ var require_boundary_checker = __commonJS({
|
|
|
12899
12926
|
return true;
|
|
12900
12927
|
if (suffixPattern("filter").test(tgtBase))
|
|
12901
12928
|
return true;
|
|
12929
|
+
if (suffixPattern("strategy").test(tgtBase))
|
|
12930
|
+
return true;
|
|
12902
12931
|
if (/\/dto\//.test(tgtBase) || suffixPattern("dto").test(tgtBase))
|
|
12903
12932
|
return true;
|
|
12904
12933
|
for (const folder of NESTJS_SHARED_INFRA_FOLDERS) {
|
|
@@ -16174,6 +16203,8 @@ var require_reliability_detector = __commonJS({
|
|
|
16174
16203
|
detectMissingNullGuard(sourceFile, file.path, fns, policy, violations);
|
|
16175
16204
|
detectMissingDtoValidation(sourceFile, file.path, fns, policy, violations);
|
|
16176
16205
|
detectUnguardedRouteTodo(sourceFile, file.path, fns, policy, violations);
|
|
16206
|
+
detectCatchSwallowsContext(sourceFile, file.path, fns, policy, violations);
|
|
16207
|
+
detectDebugConsoleLog(sourceFile, file.path, fns, policy, violations);
|
|
16177
16208
|
project.removeSourceFile(sourceFile);
|
|
16178
16209
|
}
|
|
16179
16210
|
return applyExceptions(violations, policy);
|
|
@@ -16979,6 +17010,78 @@ var require_reliability_detector = __commonJS({
|
|
|
16979
17010
|
violations.push(makeViolation(shared_1.RELIABILITY_RULES.UNGUARDED_ROUTE_TODO, filePath, node.getStartLineNumber(), `Route handler '${node.getName?.() ?? "anonymous"}' has TODO/FIXME comment and no @UseGuards() \u2014 unprotected endpoint`, policy, fn?.name, "Add @UseGuards(JwtAuthGuard) or appropriate guard \u2014 this route is currently unprotected"));
|
|
16980
17011
|
});
|
|
16981
17012
|
}
|
|
17013
|
+
var DEBUG_LOG_PATTERN = /\b(RESULT|DEBUG|TEST|TODO|FIXME|HACK|XXX)\b/i;
|
|
17014
|
+
var EXCLUDE_LOG_FILES = /\.(spec|test)\.(ts|js)|seed|migration|main\.(ts|js)$/;
|
|
17015
|
+
function detectDebugConsoleLog(sourceFile, filePath, fns, policy, violations) {
|
|
17016
|
+
if (EXCLUDE_LOG_FILES.test(filePath))
|
|
17017
|
+
return;
|
|
17018
|
+
sourceFile.forEachDescendant((node) => {
|
|
17019
|
+
if (!ts_morph_1.Node.isCallExpression(node))
|
|
17020
|
+
return;
|
|
17021
|
+
const expr = node.getExpression();
|
|
17022
|
+
if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
|
|
17023
|
+
return;
|
|
17024
|
+
const obj = expr.getExpression();
|
|
17025
|
+
if (!ts_morph_1.Node.isIdentifier(obj) || obj.getText() !== "console")
|
|
17026
|
+
return;
|
|
17027
|
+
if (expr.getName() !== "log")
|
|
17028
|
+
return;
|
|
17029
|
+
const args = node.getArguments();
|
|
17030
|
+
const argsText = args.map((a) => a.getText()).join(" ");
|
|
17031
|
+
if (!DEBUG_LOG_PATTERN.test(argsText))
|
|
17032
|
+
return;
|
|
17033
|
+
const catchClause = node.getFirstAncestorByKind(ts_morph_1.SyntaxKind.CatchClause);
|
|
17034
|
+
if (catchClause)
|
|
17035
|
+
return;
|
|
17036
|
+
const fn = getEnclosingFn(node, fns);
|
|
17037
|
+
violations.push({
|
|
17038
|
+
category: "maintainability",
|
|
17039
|
+
type: shared_1.MAINTAINABILITY_RULES.DEBUG_CONSOLE_LOG,
|
|
17040
|
+
ruleId: shared_1.MAINTAINABILITY_RULES.DEBUG_CONSOLE_LOG,
|
|
17041
|
+
severity: "warning",
|
|
17042
|
+
source: "deterministic",
|
|
17043
|
+
confidence: "high",
|
|
17044
|
+
file: filePath,
|
|
17045
|
+
line: node.getStartLineNumber(),
|
|
17046
|
+
function: fn?.name,
|
|
17047
|
+
message: `Debug console.log() in production code: ${argsText.substring(0, 60)}`,
|
|
17048
|
+
suggestion: "Remove debug logging or replace with a proper logger (winston, pino)",
|
|
17049
|
+
debtPoints: 1,
|
|
17050
|
+
gateAction: "warn"
|
|
17051
|
+
});
|
|
17052
|
+
});
|
|
17053
|
+
}
|
|
17054
|
+
function detectCatchSwallowsContext(sourceFile, filePath, fns, policy, violations) {
|
|
17055
|
+
sourceFile.forEachDescendant((node) => {
|
|
17056
|
+
if (!ts_morph_1.Node.isCatchClause(node))
|
|
17057
|
+
return;
|
|
17058
|
+
const block = node.getBlock();
|
|
17059
|
+
const errParam = node.getVariableDeclaration()?.getName();
|
|
17060
|
+
if (!errParam)
|
|
17061
|
+
return;
|
|
17062
|
+
let throwsNew = false;
|
|
17063
|
+
let referencesOriginal = false;
|
|
17064
|
+
block.forEachDescendant((child) => {
|
|
17065
|
+
if (ts_morph_1.Node.isThrowStatement(child)) {
|
|
17066
|
+
throwsNew = true;
|
|
17067
|
+
const thrownText = child.getExpression()?.getText() ?? "";
|
|
17068
|
+
if (thrownText.includes(errParam)) {
|
|
17069
|
+
referencesOriginal = true;
|
|
17070
|
+
}
|
|
17071
|
+
}
|
|
17072
|
+
});
|
|
17073
|
+
if (!throwsNew)
|
|
17074
|
+
return;
|
|
17075
|
+
if (referencesOriginal)
|
|
17076
|
+
return;
|
|
17077
|
+
const blockText = block.getText();
|
|
17078
|
+
if (/\b(logger|console\.(?:error|warn))\b/i.test(blockText) && new RegExp(`\\b${escapeRegex(errParam)}\\b`).test(blockText)) {
|
|
17079
|
+
return;
|
|
17080
|
+
}
|
|
17081
|
+
const fn = getEnclosingFn(node, fns);
|
|
17082
|
+
violations.push(makeViolation(shared_1.RELIABILITY_RULES.CATCH_SWALLOWS_CONTEXT, filePath, node.getStartLineNumber(), `Catch block throws new error without including original '${errParam}' \u2014 error context lost`, policy, fn?.name, `Include the original error: throw new Error('message', { cause: ${errParam} }) or throw new HttpException(message, status, { cause: ${errParam} })`));
|
|
17083
|
+
});
|
|
17084
|
+
}
|
|
16982
17085
|
function escapeRegex(str) {
|
|
16983
17086
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16984
17087
|
}
|