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.
- package/dist/index.js +159 -3
- 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 =
|
|
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
|
|
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 = [];
|