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