@harness-engineering/core 0.13.1 → 0.14.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.
- package/dist/architecture/matchers.js +240 -188
- package/dist/architecture/matchers.mjs +1 -1
- package/dist/{chunk-D6VFA6AS.mjs → chunk-BQUWXBGR.mjs} +240 -188
- package/dist/index.d.mts +93 -25
- package/dist/index.d.ts +93 -25
- package/dist/index.js +1609 -1182
- package/dist/index.mjs +1344 -976
- package/package.json +3 -3
|
@@ -292,65 +292,71 @@ async function validateDependencies(config) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// src/constraints/circular-deps.ts
|
|
295
|
-
function
|
|
296
|
-
const nodeMap = /* @__PURE__ */ new Map();
|
|
297
|
-
const stack = [];
|
|
298
|
-
const sccs = [];
|
|
299
|
-
let index = 0;
|
|
295
|
+
function buildAdjacencyList(graph) {
|
|
300
296
|
const adjacency = /* @__PURE__ */ new Map();
|
|
297
|
+
const nodeSet = new Set(graph.nodes);
|
|
301
298
|
for (const node of graph.nodes) {
|
|
302
299
|
adjacency.set(node, []);
|
|
303
300
|
}
|
|
304
301
|
for (const edge of graph.edges) {
|
|
305
302
|
const neighbors = adjacency.get(edge.from);
|
|
306
|
-
if (neighbors &&
|
|
303
|
+
if (neighbors && nodeSet.has(edge.to)) {
|
|
307
304
|
neighbors.push(edge.to);
|
|
308
305
|
}
|
|
309
306
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
307
|
+
return adjacency;
|
|
308
|
+
}
|
|
309
|
+
function isCyclicSCC(scc, adjacency) {
|
|
310
|
+
if (scc.length > 1) return true;
|
|
311
|
+
if (scc.length === 1) {
|
|
312
|
+
const selfNode = scc[0];
|
|
313
|
+
const selfNeighbors = adjacency.get(selfNode) ?? [];
|
|
314
|
+
return selfNeighbors.includes(selfNode);
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
function processNeighbors(node, neighbors, nodeMap, stack, adjacency, sccs, indexRef) {
|
|
319
|
+
for (const neighbor of neighbors) {
|
|
320
|
+
const neighborData = nodeMap.get(neighbor);
|
|
321
|
+
if (!neighborData) {
|
|
322
|
+
strongConnectImpl(neighbor, nodeMap, stack, adjacency, sccs, indexRef);
|
|
323
|
+
const nodeData = nodeMap.get(node);
|
|
324
|
+
const updatedNeighborData = nodeMap.get(neighbor);
|
|
325
|
+
nodeData.lowlink = Math.min(nodeData.lowlink, updatedNeighborData.lowlink);
|
|
326
|
+
} else if (neighborData.onStack) {
|
|
327
|
+
const nodeData = nodeMap.get(node);
|
|
328
|
+
nodeData.lowlink = Math.min(nodeData.lowlink, neighborData.index);
|
|
330
329
|
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function strongConnectImpl(node, nodeMap, stack, adjacency, sccs, indexRef) {
|
|
333
|
+
nodeMap.set(node, { index: indexRef.value, lowlink: indexRef.value, onStack: true });
|
|
334
|
+
indexRef.value++;
|
|
335
|
+
stack.push(node);
|
|
336
|
+
processNeighbors(node, adjacency.get(node) ?? [], nodeMap, stack, adjacency, sccs, indexRef);
|
|
337
|
+
const nodeData = nodeMap.get(node);
|
|
338
|
+
if (nodeData.lowlink === nodeData.index) {
|
|
339
|
+
const scc = [];
|
|
340
|
+
let w;
|
|
341
|
+
do {
|
|
342
|
+
w = stack.pop();
|
|
343
|
+
nodeMap.get(w).onStack = false;
|
|
344
|
+
scc.push(w);
|
|
345
|
+
} while (w !== node);
|
|
346
|
+
if (isCyclicSCC(scc, adjacency)) {
|
|
347
|
+
sccs.push(scc);
|
|
349
348
|
}
|
|
350
349
|
}
|
|
350
|
+
}
|
|
351
|
+
function tarjanSCC(graph) {
|
|
352
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
353
|
+
const stack = [];
|
|
354
|
+
const sccs = [];
|
|
355
|
+
const indexRef = { value: 0 };
|
|
356
|
+
const adjacency = buildAdjacencyList(graph);
|
|
351
357
|
for (const node of graph.nodes) {
|
|
352
358
|
if (!nodeMap.has(node)) {
|
|
353
|
-
|
|
359
|
+
strongConnectImpl(node, nodeMap, stack, adjacency, sccs, indexRef);
|
|
354
360
|
}
|
|
355
361
|
}
|
|
356
362
|
return sccs;
|
|
@@ -620,6 +626,31 @@ function aggregateByCategory(results) {
|
|
|
620
626
|
}
|
|
621
627
|
return map;
|
|
622
628
|
}
|
|
629
|
+
function classifyViolations(violations, baselineViolationIds) {
|
|
630
|
+
const newViolations = [];
|
|
631
|
+
const preExisting = [];
|
|
632
|
+
for (const violation of violations) {
|
|
633
|
+
if (baselineViolationIds.has(violation.id)) {
|
|
634
|
+
preExisting.push(violation.id);
|
|
635
|
+
} else {
|
|
636
|
+
newViolations.push(violation);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return { newViolations, preExisting };
|
|
640
|
+
}
|
|
641
|
+
function findResolvedViolations(baselineCategory, currentViolationIds) {
|
|
642
|
+
if (!baselineCategory) return [];
|
|
643
|
+
return baselineCategory.violationIds.filter((id) => !currentViolationIds.has(id));
|
|
644
|
+
}
|
|
645
|
+
function collectOrphanedBaselineViolations(baseline, visitedCategories) {
|
|
646
|
+
const resolved = [];
|
|
647
|
+
for (const [category, baselineCategory] of Object.entries(baseline.metrics)) {
|
|
648
|
+
if (!visitedCategories.has(category) && baselineCategory) {
|
|
649
|
+
resolved.push(...baselineCategory.violationIds);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
return resolved;
|
|
653
|
+
}
|
|
623
654
|
function diff(current, baseline) {
|
|
624
655
|
const aggregated = aggregateByCategory(current);
|
|
625
656
|
const newViolations = [];
|
|
@@ -632,21 +663,11 @@ function diff(current, baseline) {
|
|
|
632
663
|
const baselineCategory = baseline.metrics[category];
|
|
633
664
|
const baselineViolationIds = new Set(baselineCategory?.violationIds ?? []);
|
|
634
665
|
const baselineValue = baselineCategory?.value ?? 0;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
} else {
|
|
639
|
-
newViolations.push(violation);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
666
|
+
const classified = classifyViolations(agg.violations, baselineViolationIds);
|
|
667
|
+
newViolations.push(...classified.newViolations);
|
|
668
|
+
preExisting.push(...classified.preExisting);
|
|
642
669
|
const currentViolationIds = new Set(agg.violations.map((v) => v.id));
|
|
643
|
-
|
|
644
|
-
for (const id of baselineCategory.violationIds) {
|
|
645
|
-
if (!currentViolationIds.has(id)) {
|
|
646
|
-
resolvedViolations.push(id);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
}
|
|
670
|
+
resolvedViolations.push(...findResolvedViolations(baselineCategory, currentViolationIds));
|
|
650
671
|
if (baselineCategory && agg.value > baselineValue) {
|
|
651
672
|
regressions.push({
|
|
652
673
|
category,
|
|
@@ -656,16 +677,9 @@ function diff(current, baseline) {
|
|
|
656
677
|
});
|
|
657
678
|
}
|
|
658
679
|
}
|
|
659
|
-
|
|
660
|
-
if (!visitedCategories.has(category) && baselineCategory) {
|
|
661
|
-
for (const id of baselineCategory.violationIds) {
|
|
662
|
-
resolvedViolations.push(id);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
const passed = newViolations.length === 0 && regressions.length === 0;
|
|
680
|
+
resolvedViolations.push(...collectOrphanedBaselineViolations(baseline, visitedCategories));
|
|
667
681
|
return {
|
|
668
|
-
passed,
|
|
682
|
+
passed: newViolations.length === 0 && regressions.length === 0,
|
|
669
683
|
newViolations,
|
|
670
684
|
resolvedViolations,
|
|
671
685
|
preExisting,
|
|
@@ -683,22 +697,22 @@ var DEFAULT_THRESHOLDS = {
|
|
|
683
697
|
fileLength: { info: 300 },
|
|
684
698
|
hotspotPercentile: { error: 95 }
|
|
685
699
|
};
|
|
700
|
+
var FUNCTION_PATTERNS = [
|
|
701
|
+
// function declarations: function name(params) {
|
|
702
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
|
|
703
|
+
// method declarations: name(params) {
|
|
704
|
+
/^\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/,
|
|
705
|
+
// arrow functions assigned to const/let/var: const name = (params) =>
|
|
706
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/,
|
|
707
|
+
// arrow functions assigned to const/let/var with single param: const name = param =>
|
|
708
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(\w+)\s*=>/
|
|
709
|
+
];
|
|
686
710
|
function extractFunctions(content) {
|
|
687
711
|
const functions = [];
|
|
688
712
|
const lines = content.split("\n");
|
|
689
|
-
const patterns = [
|
|
690
|
-
// function declarations: function name(params) {
|
|
691
|
-
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/,
|
|
692
|
-
// method declarations: name(params) {
|
|
693
|
-
/^\s*(?:async\s+)?(\w+)\s*\(([^)]*)\)\s*(?::\s*[^{]+)?\s*\{/,
|
|
694
|
-
// arrow functions assigned to const/let/var: const name = (params) =>
|
|
695
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*[^=]+)?\s*=>/,
|
|
696
|
-
// arrow functions assigned to const/let/var with single param: const name = param =>
|
|
697
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(\w+)\s*=>/
|
|
698
|
-
];
|
|
699
713
|
for (let i = 0; i < lines.length; i++) {
|
|
700
714
|
const line = lines[i];
|
|
701
|
-
for (const pattern of
|
|
715
|
+
for (const pattern of FUNCTION_PATTERNS) {
|
|
702
716
|
const match = line.match(pattern);
|
|
703
717
|
if (match) {
|
|
704
718
|
const name = match[1] ?? "anonymous";
|
|
@@ -787,26 +801,155 @@ function computeNestingDepth(body) {
|
|
|
787
801
|
}
|
|
788
802
|
return maxDepth;
|
|
789
803
|
}
|
|
790
|
-
|
|
791
|
-
const
|
|
792
|
-
|
|
804
|
+
function resolveThresholds(config) {
|
|
805
|
+
const userThresholds = config?.thresholds;
|
|
806
|
+
if (!userThresholds) return { ...DEFAULT_THRESHOLDS };
|
|
807
|
+
return {
|
|
793
808
|
cyclomaticComplexity: {
|
|
794
|
-
|
|
795
|
-
|
|
809
|
+
...DEFAULT_THRESHOLDS.cyclomaticComplexity,
|
|
810
|
+
...stripUndefined(userThresholds.cyclomaticComplexity)
|
|
796
811
|
},
|
|
797
812
|
nestingDepth: {
|
|
798
|
-
|
|
813
|
+
...DEFAULT_THRESHOLDS.nestingDepth,
|
|
814
|
+
...stripUndefined(userThresholds.nestingDepth)
|
|
799
815
|
},
|
|
800
816
|
functionLength: {
|
|
801
|
-
|
|
817
|
+
...DEFAULT_THRESHOLDS.functionLength,
|
|
818
|
+
...stripUndefined(userThresholds.functionLength)
|
|
802
819
|
},
|
|
803
820
|
parameterCount: {
|
|
804
|
-
|
|
821
|
+
...DEFAULT_THRESHOLDS.parameterCount,
|
|
822
|
+
...stripUndefined(userThresholds.parameterCount)
|
|
805
823
|
},
|
|
806
|
-
fileLength: {
|
|
807
|
-
info: config?.thresholds?.fileLength?.info ?? DEFAULT_THRESHOLDS.fileLength.info
|
|
808
|
-
}
|
|
824
|
+
fileLength: { ...DEFAULT_THRESHOLDS.fileLength, ...stripUndefined(userThresholds.fileLength) }
|
|
809
825
|
};
|
|
826
|
+
}
|
|
827
|
+
function stripUndefined(obj) {
|
|
828
|
+
if (!obj) return {};
|
|
829
|
+
const result = {};
|
|
830
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
831
|
+
if (val !== void 0) result[key] = val;
|
|
832
|
+
}
|
|
833
|
+
return result;
|
|
834
|
+
}
|
|
835
|
+
function checkFileLengthViolation(filePath, lineCount, threshold) {
|
|
836
|
+
if (lineCount <= threshold) return null;
|
|
837
|
+
return {
|
|
838
|
+
file: filePath,
|
|
839
|
+
function: "<file>",
|
|
840
|
+
line: 1,
|
|
841
|
+
metric: "fileLength",
|
|
842
|
+
value: lineCount,
|
|
843
|
+
threshold,
|
|
844
|
+
tier: 3,
|
|
845
|
+
severity: "info",
|
|
846
|
+
message: `File has ${lineCount} lines (threshold: ${threshold})`
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function checkCyclomaticComplexity(filePath, fn, thresholds) {
|
|
850
|
+
const complexity = computeCyclomaticComplexity(fn.body);
|
|
851
|
+
if (complexity > thresholds.error) {
|
|
852
|
+
return {
|
|
853
|
+
file: filePath,
|
|
854
|
+
function: fn.name,
|
|
855
|
+
line: fn.line,
|
|
856
|
+
metric: "cyclomaticComplexity",
|
|
857
|
+
value: complexity,
|
|
858
|
+
threshold: thresholds.error,
|
|
859
|
+
tier: 1,
|
|
860
|
+
severity: "error",
|
|
861
|
+
message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (error threshold: ${thresholds.error})`
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
if (complexity > thresholds.warn) {
|
|
865
|
+
return {
|
|
866
|
+
file: filePath,
|
|
867
|
+
function: fn.name,
|
|
868
|
+
line: fn.line,
|
|
869
|
+
metric: "cyclomaticComplexity",
|
|
870
|
+
value: complexity,
|
|
871
|
+
threshold: thresholds.warn,
|
|
872
|
+
tier: 2,
|
|
873
|
+
severity: "warning",
|
|
874
|
+
message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (warning threshold: ${thresholds.warn})`
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
function checkNestingDepth(filePath, fn, threshold) {
|
|
880
|
+
const depth = computeNestingDepth(fn.body);
|
|
881
|
+
if (depth <= threshold) return null;
|
|
882
|
+
return {
|
|
883
|
+
file: filePath,
|
|
884
|
+
function: fn.name,
|
|
885
|
+
line: fn.line,
|
|
886
|
+
metric: "nestingDepth",
|
|
887
|
+
value: depth,
|
|
888
|
+
threshold,
|
|
889
|
+
tier: 2,
|
|
890
|
+
severity: "warning",
|
|
891
|
+
message: `Function "${fn.name}" has nesting depth of ${depth} (threshold: ${threshold})`
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function checkFunctionLength(filePath, fn, threshold) {
|
|
895
|
+
const fnLength = fn.endLine - fn.startLine + 1;
|
|
896
|
+
if (fnLength <= threshold) return null;
|
|
897
|
+
return {
|
|
898
|
+
file: filePath,
|
|
899
|
+
function: fn.name,
|
|
900
|
+
line: fn.line,
|
|
901
|
+
metric: "functionLength",
|
|
902
|
+
value: fnLength,
|
|
903
|
+
threshold,
|
|
904
|
+
tier: 2,
|
|
905
|
+
severity: "warning",
|
|
906
|
+
message: `Function "${fn.name}" is ${fnLength} lines long (threshold: ${threshold})`
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
function checkParameterCount(filePath, fn, threshold) {
|
|
910
|
+
if (fn.params <= threshold) return null;
|
|
911
|
+
return {
|
|
912
|
+
file: filePath,
|
|
913
|
+
function: fn.name,
|
|
914
|
+
line: fn.line,
|
|
915
|
+
metric: "parameterCount",
|
|
916
|
+
value: fn.params,
|
|
917
|
+
threshold,
|
|
918
|
+
tier: 2,
|
|
919
|
+
severity: "warning",
|
|
920
|
+
message: `Function "${fn.name}" has ${fn.params} parameters (threshold: ${threshold})`
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function checkHotspot(filePath, fn, graphData) {
|
|
924
|
+
const hotspot = graphData.hotspots.find((h) => h.file === filePath && h.function === fn.name);
|
|
925
|
+
if (!hotspot || hotspot.hotspotScore <= graphData.percentile95Score) return null;
|
|
926
|
+
return {
|
|
927
|
+
file: filePath,
|
|
928
|
+
function: fn.name,
|
|
929
|
+
line: fn.line,
|
|
930
|
+
metric: "hotspotScore",
|
|
931
|
+
value: hotspot.hotspotScore,
|
|
932
|
+
threshold: graphData.percentile95Score,
|
|
933
|
+
tier: 1,
|
|
934
|
+
severity: "error",
|
|
935
|
+
message: `Function "${fn.name}" is a complexity hotspot (score: ${hotspot.hotspotScore}, p95: ${graphData.percentile95Score})`
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function collectFunctionViolations(filePath, fn, thresholds, graphData) {
|
|
939
|
+
const checks = [
|
|
940
|
+
checkCyclomaticComplexity(filePath, fn, thresholds.cyclomaticComplexity),
|
|
941
|
+
checkNestingDepth(filePath, fn, thresholds.nestingDepth.warn),
|
|
942
|
+
checkFunctionLength(filePath, fn, thresholds.functionLength.warn),
|
|
943
|
+
checkParameterCount(filePath, fn, thresholds.parameterCount.warn)
|
|
944
|
+
];
|
|
945
|
+
if (graphData) {
|
|
946
|
+
checks.push(checkHotspot(filePath, fn, graphData));
|
|
947
|
+
}
|
|
948
|
+
return checks.filter((v) => v !== null);
|
|
949
|
+
}
|
|
950
|
+
async function detectComplexityViolations(snapshot, config, graphData) {
|
|
951
|
+
const violations = [];
|
|
952
|
+
const thresholds = resolveThresholds(config);
|
|
810
953
|
let totalFunctions = 0;
|
|
811
954
|
for (const file of snapshot.files) {
|
|
812
955
|
let content;
|
|
@@ -816,107 +959,16 @@ async function detectComplexityViolations(snapshot, config, graphData) {
|
|
|
816
959
|
continue;
|
|
817
960
|
}
|
|
818
961
|
const lines = content.split("\n");
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
value: lines.length,
|
|
826
|
-
threshold: thresholds.fileLength.info,
|
|
827
|
-
tier: 3,
|
|
828
|
-
severity: "info",
|
|
829
|
-
message: `File has ${lines.length} lines (threshold: ${thresholds.fileLength.info})`
|
|
830
|
-
});
|
|
831
|
-
}
|
|
962
|
+
const fileLenViolation = checkFileLengthViolation(
|
|
963
|
+
file.path,
|
|
964
|
+
lines.length,
|
|
965
|
+
thresholds.fileLength.info
|
|
966
|
+
);
|
|
967
|
+
if (fileLenViolation) violations.push(fileLenViolation);
|
|
832
968
|
const functions = extractFunctions(content);
|
|
833
969
|
totalFunctions += functions.length;
|
|
834
970
|
for (const fn of functions) {
|
|
835
|
-
|
|
836
|
-
if (complexity > thresholds.cyclomaticComplexity.error) {
|
|
837
|
-
violations.push({
|
|
838
|
-
file: file.path,
|
|
839
|
-
function: fn.name,
|
|
840
|
-
line: fn.line,
|
|
841
|
-
metric: "cyclomaticComplexity",
|
|
842
|
-
value: complexity,
|
|
843
|
-
threshold: thresholds.cyclomaticComplexity.error,
|
|
844
|
-
tier: 1,
|
|
845
|
-
severity: "error",
|
|
846
|
-
message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (error threshold: ${thresholds.cyclomaticComplexity.error})`
|
|
847
|
-
});
|
|
848
|
-
} else if (complexity > thresholds.cyclomaticComplexity.warn) {
|
|
849
|
-
violations.push({
|
|
850
|
-
file: file.path,
|
|
851
|
-
function: fn.name,
|
|
852
|
-
line: fn.line,
|
|
853
|
-
metric: "cyclomaticComplexity",
|
|
854
|
-
value: complexity,
|
|
855
|
-
threshold: thresholds.cyclomaticComplexity.warn,
|
|
856
|
-
tier: 2,
|
|
857
|
-
severity: "warning",
|
|
858
|
-
message: `Function "${fn.name}" has cyclomatic complexity of ${complexity} (warning threshold: ${thresholds.cyclomaticComplexity.warn})`
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
const nestingDepth = computeNestingDepth(fn.body);
|
|
862
|
-
if (nestingDepth > thresholds.nestingDepth.warn) {
|
|
863
|
-
violations.push({
|
|
864
|
-
file: file.path,
|
|
865
|
-
function: fn.name,
|
|
866
|
-
line: fn.line,
|
|
867
|
-
metric: "nestingDepth",
|
|
868
|
-
value: nestingDepth,
|
|
869
|
-
threshold: thresholds.nestingDepth.warn,
|
|
870
|
-
tier: 2,
|
|
871
|
-
severity: "warning",
|
|
872
|
-
message: `Function "${fn.name}" has nesting depth of ${nestingDepth} (threshold: ${thresholds.nestingDepth.warn})`
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
const fnLength = fn.endLine - fn.startLine + 1;
|
|
876
|
-
if (fnLength > thresholds.functionLength.warn) {
|
|
877
|
-
violations.push({
|
|
878
|
-
file: file.path,
|
|
879
|
-
function: fn.name,
|
|
880
|
-
line: fn.line,
|
|
881
|
-
metric: "functionLength",
|
|
882
|
-
value: fnLength,
|
|
883
|
-
threshold: thresholds.functionLength.warn,
|
|
884
|
-
tier: 2,
|
|
885
|
-
severity: "warning",
|
|
886
|
-
message: `Function "${fn.name}" is ${fnLength} lines long (threshold: ${thresholds.functionLength.warn})`
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
if (fn.params > thresholds.parameterCount.warn) {
|
|
890
|
-
violations.push({
|
|
891
|
-
file: file.path,
|
|
892
|
-
function: fn.name,
|
|
893
|
-
line: fn.line,
|
|
894
|
-
metric: "parameterCount",
|
|
895
|
-
value: fn.params,
|
|
896
|
-
threshold: thresholds.parameterCount.warn,
|
|
897
|
-
tier: 2,
|
|
898
|
-
severity: "warning",
|
|
899
|
-
message: `Function "${fn.name}" has ${fn.params} parameters (threshold: ${thresholds.parameterCount.warn})`
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
if (graphData) {
|
|
903
|
-
const hotspot = graphData.hotspots.find(
|
|
904
|
-
(h) => h.file === file.path && h.function === fn.name
|
|
905
|
-
);
|
|
906
|
-
if (hotspot && hotspot.hotspotScore > graphData.percentile95Score) {
|
|
907
|
-
violations.push({
|
|
908
|
-
file: file.path,
|
|
909
|
-
function: fn.name,
|
|
910
|
-
line: fn.line,
|
|
911
|
-
metric: "hotspotScore",
|
|
912
|
-
value: hotspot.hotspotScore,
|
|
913
|
-
threshold: graphData.percentile95Score,
|
|
914
|
-
tier: 1,
|
|
915
|
-
severity: "error",
|
|
916
|
-
message: `Function "${fn.name}" is a complexity hotspot (score: ${hotspot.hotspotScore}, p95: ${graphData.percentile95Score})`
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
}
|
|
971
|
+
violations.push(...collectFunctionViolations(file.path, fn, thresholds, graphData));
|
|
920
972
|
}
|
|
921
973
|
}
|
|
922
974
|
const errorCount = violations.filter((v) => v.severity === "error").length;
|