technical-debt-radar 1.6.3 → 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 +142 -2
  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*\(/);
@@ -17022,7 +17074,7 @@ var require_reliability_detector = __commonJS({
17022
17074
  });
17023
17075
  }
17024
17076
  var ROUTE_DECORATORS = /* @__PURE__ */ new Set(["Get", "Post", "Put", "Delete", "Patch", "All", "Head", "Options"]);
17025
- var GUARD_TODO_PATTERN = /TODO|FIXME|HACK|guard|auth|security|protected/i;
17077
+ var GUARD_TODO_PATTERN = /\b(TODO|FIXME|HACK|XXX)\b/i;
17026
17078
  function detectUnguardedRouteTodo(sourceFile, filePath, fns, policy, violations) {
17027
17079
  if (!filePath.includes("controller"))
17028
17080
  return;
@@ -18710,6 +18762,12 @@ var require_dead_code_detector = __commonJS({
18710
18762
  if (aliasResolved)
18711
18763
  return aliasResolved;
18712
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
+ }
18713
18771
  const directResolved = resolveInProject(specifier, project);
18714
18772
  if (directResolved)
18715
18773
  return directResolved;
@@ -19184,7 +19242,7 @@ var require_orchestrator = __commonJS({
19184
19242
  }
19185
19243
  const complexityViolations = complexityDeltasToViolations(complexityDeltas, filteredInput);
19186
19244
  const largeFileViolations = detectLargeFiles(filteredInput);
19187
- const violations = [
19245
+ const rawViolations = [
19188
19246
  ...boundaryViolations,
19189
19247
  ...circularViolations,
19190
19248
  ...runtimeViolations,
@@ -19198,6 +19256,7 @@ var require_orchestrator = __commonJS({
19198
19256
  ...coverageDeltaResult?.violations ?? [],
19199
19257
  ...crossFileAnalysis?.violations ?? []
19200
19258
  ];
19259
+ const violations = deduplicateViolations(rawViolations);
19201
19260
  const suspectFunctions = selectSuspectFunctions(violations, complexityDeltas, filteredInput);
19202
19261
  const metrics = computeMetrics(filteredInput, importGraph, violations, complexityDeltas, suspectFunctions, startMs, duplicationResult, missingTestsResult, deadCodeResult, crossFileAnalysis, coverageDeltaResult);
19203
19262
  return {
@@ -19209,6 +19268,87 @@ var require_orchestrator = __commonJS({
19209
19268
  crossFileAnalysis
19210
19269
  };
19211
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
+ }
19212
19352
  function detectLargeFiles(input) {
19213
19353
  const threshold = shared_1.DEFAULT_LARGE_FILE_THRESHOLD;
19214
19354
  const violations = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "technical-debt-radar",
3
- "version": "1.6.3",
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",