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.
Files changed (2) hide show
  1. package/dist/index.js +111 -8
  2. 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 (!specifier.startsWith(".") && !specifier.startsWith("/")) {
12767
- return void 0;
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 sourceDir = posixDirname(sourcePath);
12770
- const resolved = posixJoinAndNormalize(sourceDir, specifier);
12771
- return resolveInProject(resolved, project) ?? resolved;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Stop Node.js production crashes before merge. 47 detection patterns across 5 categories.",
5
5
  "bin": {
6
6
  "radar": "dist/index.js",