technical-debt-radar 1.6.2 → 1.6.4

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 +159 -3
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15200,6 +15200,47 @@ var require_perf_pattern_detector = __commonJS({
15200
15200
  }
15201
15201
  return void 0;
15202
15202
  }
15203
+ function isServiceMethodCall(callExpr) {
15204
+ const expr = callExpr.getExpression();
15205
+ if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
15206
+ return false;
15207
+ const obj = expr.getExpression();
15208
+ if (ts_morph_1.Node.isPropertyAccessExpression(obj)) {
15209
+ const propName = obj.getName();
15210
+ if (/Service$/i.test(propName))
15211
+ return true;
15212
+ }
15213
+ if (ts_morph_1.Node.isIdentifier(obj)) {
15214
+ const name = obj.getText();
15215
+ if (/Service$/i.test(name))
15216
+ return true;
15217
+ }
15218
+ return false;
15219
+ }
15220
+ var BOUNDED_QUERY_METHODS = /* @__PURE__ */ new Set([
15221
+ "findOne",
15222
+ "findById",
15223
+ "findByIdAndUpdate",
15224
+ "findByIdAndDelete",
15225
+ "findOneAndUpdate",
15226
+ "findOneAndDelete",
15227
+ "findOneAndReplace",
15228
+ "findOneAndRemove",
15229
+ "findByPk",
15230
+ // Sequelize
15231
+ "findUnique",
15232
+ "findUniqueOrThrow",
15233
+ "findFirst",
15234
+ "findFirstOrThrow",
15235
+ // Prisma
15236
+ "first",
15237
+ // Knex
15238
+ "get",
15239
+ // DynamoDB
15240
+ "getOne",
15241
+ "getRawOne"
15242
+ // TypeORM QueryBuilder
15243
+ ]);
15203
15244
  var FIND_MANY_METHODS = /* @__PURE__ */ new Set(["findMany", "find", "getMany", "findAndCount"]);
15204
15245
  function detectUnboundedFindMany(sourceFile, filePath, fns, policy, violations) {
15205
15246
  sourceFile.forEachDescendant((node) => {
@@ -15209,6 +15250,10 @@ var require_perf_pattern_detector = __commonJS({
15209
15250
  if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
15210
15251
  return;
15211
15252
  const methodName = expr.getName();
15253
+ if (BOUNDED_QUERY_METHODS.has(methodName))
15254
+ return;
15255
+ if (isServiceMethodCall(node))
15256
+ return;
15212
15257
  if (TYPEORM_QB_TERMINAL.has(methodName) && methodName !== "getCount" && methodName !== "getOne" && methodName !== "getRawOne") {
15213
15258
  const chain = traceQueryBuilderChain(node);
15214
15259
  if (chain.hasTake)
@@ -15300,6 +15345,10 @@ var require_perf_pattern_detector = __commonJS({
15300
15345
  if (!ts_morph_1.Node.isPropertyAccessExpression(expr))
15301
15346
  return;
15302
15347
  const methodName = expr.getName();
15348
+ if (BOUNDED_QUERY_METHODS.has(methodName))
15349
+ return;
15350
+ if (isServiceMethodCall(node))
15351
+ return;
15303
15352
  if (TYPEORM_QB_TERMINAL.has(methodName) && methodName !== "getCount" && methodName !== "getOne" && methodName !== "getRawOne") {
15304
15353
  const chain = traceQueryBuilderChain(node);
15305
15354
  if (chain.hasWhere)
@@ -15876,6 +15925,9 @@ var require_perf_pattern_detector = __commonJS({
15876
15925
  const bodyText = fn.node.getText();
15877
15926
  if (!/\.(findMany|find|getMany|findAll|from)\s*\(/.test(bodyText) && !/knex\s*\(/.test(bodyText))
15878
15927
  continue;
15928
+ const findCallMatches = [...bodyText.matchAll(/(\w+)\.(findMany|find|getMany|findAll|from)\s*\(/g)];
15929
+ if (findCallMatches.length > 0 && findCallMatches.every((m) => /Service$/i.test(m[1])))
15930
+ continue;
15879
15931
  if (/page|skip|offset|cursor|take|limit|per_page|pageSize/i.test(bodyText))
15880
15932
  continue;
15881
15933
  const entityMatch = bodyText.match(/(\w+)\.(findMany|find|getMany|findAll)\s*\(/);
@@ -16219,7 +16271,23 @@ var require_reliability_detector = __commonJS({
16219
16271
  detectUnboundedParallelCalls(sourceFile, file.path, fns, policy, violations);
16220
16272
  project.removeSourceFile(sourceFile);
16221
16273
  }
16222
- return applyExceptions(violations, policy);
16274
+ return applyExceptions(deduplicateOverlapping(violations), policy);
16275
+ }
16276
+ function deduplicateOverlapping(violations) {
16277
+ const hasMissingTryCatch = /* @__PURE__ */ new Set();
16278
+ for (const v of violations) {
16279
+ if (v.ruleId === shared_1.RELIABILITY_RULES.MISSING_TRY_CATCH) {
16280
+ hasMissingTryCatch.add(`${v.file}:${v.function ?? ""}`);
16281
+ }
16282
+ }
16283
+ return violations.filter((v) => {
16284
+ if (v.ruleId === shared_1.RELIABILITY_RULES.UNHANDLED_PROMISE_REJECTION) {
16285
+ const key = `${v.file}:${v.function ?? ""}`;
16286
+ if (hasMissingTryCatch.has(key))
16287
+ return false;
16288
+ }
16289
+ return true;
16290
+ });
16223
16291
  }
16224
16292
  function collectFunctions(sourceFile, filePath) {
16225
16293
  const scopes = [];
@@ -17006,7 +17074,7 @@ var require_reliability_detector = __commonJS({
17006
17074
  });
17007
17075
  }
17008
17076
  var ROUTE_DECORATORS = /* @__PURE__ */ new Set(["Get", "Post", "Put", "Delete", "Patch", "All", "Head", "Options"]);
17009
- var GUARD_TODO_PATTERN = /TODO|FIXME|HACK|guard|auth|security|protected/i;
17077
+ var GUARD_TODO_PATTERN = /\b(TODO|FIXME|HACK|XXX)\b/i;
17010
17078
  function detectUnguardedRouteTodo(sourceFile, filePath, fns, policy, violations) {
17011
17079
  if (!filePath.includes("controller"))
17012
17080
  return;
@@ -18694,6 +18762,12 @@ var require_dead_code_detector = __commonJS({
18694
18762
  if (aliasResolved)
18695
18763
  return aliasResolved;
18696
18764
  }
18765
+ const aliasStripped = specifier.replace(/^@\//, "src/").replace(/^~\//, "src/");
18766
+ if (aliasStripped !== specifier) {
18767
+ const aliasResolved = resolveInProject(aliasStripped, project);
18768
+ if (aliasResolved)
18769
+ return aliasResolved;
18770
+ }
18697
18771
  const directResolved = resolveInProject(specifier, project);
18698
18772
  if (directResolved)
18699
18773
  return directResolved;
@@ -19168,7 +19242,7 @@ var require_orchestrator = __commonJS({
19168
19242
  }
19169
19243
  const complexityViolations = complexityDeltasToViolations(complexityDeltas, filteredInput);
19170
19244
  const largeFileViolations = detectLargeFiles(filteredInput);
19171
- const violations = [
19245
+ const rawViolations = [
19172
19246
  ...boundaryViolations,
19173
19247
  ...circularViolations,
19174
19248
  ...runtimeViolations,
@@ -19182,6 +19256,7 @@ var require_orchestrator = __commonJS({
19182
19256
  ...coverageDeltaResult?.violations ?? [],
19183
19257
  ...crossFileAnalysis?.violations ?? []
19184
19258
  ];
19259
+ const violations = deduplicateViolations(rawViolations);
19185
19260
  const suspectFunctions = selectSuspectFunctions(violations, complexityDeltas, filteredInput);
19186
19261
  const metrics = computeMetrics(filteredInput, importGraph, violations, complexityDeltas, suspectFunctions, startMs, duplicationResult, missingTestsResult, deadCodeResult, crossFileAnalysis, coverageDeltaResult);
19187
19262
  return {
@@ -19193,6 +19268,87 @@ var require_orchestrator = __commonJS({
19193
19268
  crossFileAnalysis
19194
19269
  };
19195
19270
  }
19271
+ var PERF_DEDUP_RULES = /* @__PURE__ */ new Set([
19272
+ "unbounded-find-many",
19273
+ "find-many-no-where",
19274
+ "missing-pagination-endpoint"
19275
+ ]);
19276
+ function deduplicateViolations(violations) {
19277
+ let result = violations;
19278
+ result = deduplicateControllerService(result);
19279
+ result = deduplicateNestedScopes(result);
19280
+ return result;
19281
+ }
19282
+ function deduplicateControllerService(violations) {
19283
+ const perfViolations = violations.filter((v) => PERF_DEDUP_RULES.has(v.ruleId));
19284
+ if (perfViolations.length === 0)
19285
+ return violations;
19286
+ const serviceViolationKeys = /* @__PURE__ */ new Set();
19287
+ for (const v of perfViolations) {
19288
+ if (/\.service\./i.test(v.file)) {
19289
+ serviceViolationKeys.add(v.ruleId);
19290
+ }
19291
+ }
19292
+ const serviceByRuleAndDir = /* @__PURE__ */ new Map();
19293
+ for (const v of perfViolations) {
19294
+ if (/\.service\./i.test(v.file)) {
19295
+ const dir = v.file.substring(0, v.file.lastIndexOf("/") + 1);
19296
+ const key = `${v.ruleId}::${dir}`;
19297
+ if (!serviceByRuleAndDir.has(key))
19298
+ serviceByRuleAndDir.set(key, /* @__PURE__ */ new Set());
19299
+ serviceByRuleAndDir.get(key).add(v.function ?? "");
19300
+ }
19301
+ }
19302
+ return violations.filter((v) => {
19303
+ if (!PERF_DEDUP_RULES.has(v.ruleId))
19304
+ return true;
19305
+ if (!/\.controller\./i.test(v.file))
19306
+ return true;
19307
+ const dir = v.file.substring(0, v.file.lastIndexOf("/") + 1);
19308
+ const key = `${v.ruleId}::${dir}`;
19309
+ if (serviceByRuleAndDir.has(key))
19310
+ return false;
19311
+ if (serviceViolationKeys.has(v.ruleId) && /\.controller\./i.test(v.file))
19312
+ return false;
19313
+ return true;
19314
+ });
19315
+ }
19316
+ var NESTED_SCOPE_DEDUP_RULES = /* @__PURE__ */ new Set([
19317
+ "missing-try-catch",
19318
+ "unhandled-promise-rejection"
19319
+ ]);
19320
+ function deduplicateNestedScopes(violations) {
19321
+ const groups = /* @__PURE__ */ new Map();
19322
+ for (const v of violations) {
19323
+ if (!NESTED_SCOPE_DEDUP_RULES.has(v.ruleId))
19324
+ continue;
19325
+ const key = `${v.file}::${v.ruleId}`;
19326
+ if (!groups.has(key))
19327
+ groups.set(key, []);
19328
+ groups.get(key).push(v);
19329
+ }
19330
+ const toRemove = /* @__PURE__ */ new Set();
19331
+ for (const [, group] of groups) {
19332
+ if (group.length < 2)
19333
+ continue;
19334
+ const byFunction = /* @__PURE__ */ new Map();
19335
+ for (const v of group) {
19336
+ const fn = v.function ?? "";
19337
+ if (!byFunction.has(fn))
19338
+ byFunction.set(fn, []);
19339
+ byFunction.get(fn).push(v);
19340
+ }
19341
+ for (const [, fnGroup] of byFunction) {
19342
+ if (fnGroup.length < 2)
19343
+ continue;
19344
+ fnGroup.sort((a, b) => a.line - b.line);
19345
+ for (let i = 1; i < fnGroup.length; i++) {
19346
+ toRemove.add(fnGroup[i]);
19347
+ }
19348
+ }
19349
+ }
19350
+ return violations.filter((v) => !toRemove.has(v));
19351
+ }
19196
19352
  function detectLargeFiles(input) {
19197
19353
  const threshold = shared_1.DEFAULT_LARGE_FILE_THRESHOLD;
19198
19354
  const violations = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
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",