@deplens/mcp 0.1.7 → 0.1.8
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/bin/deplens-mcp.js +0 -0
- package/package.json +6 -3
- package/src/core/changelog-parser.mjs +313 -0
- package/src/core/diff-analyzer.mjs +590 -0
- package/src/core/diff.mjs +145 -0
- package/src/core/inspect.mjs +950 -482
- package/src/core/parse-dts.mjs +198 -172
- package/src/core/parse-source.mjs +524 -0
- package/src/core/version-resolver.mjs +317 -0
- package/src/server.mjs +581 -37
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* parse-source.mjs - Source code analysis for .ts/.js files
|
|
3
|
+
* Extracts implementation details beyond type signatures
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calculate cyclomatic complexity of a function
|
|
12
|
+
* Counts decision points: if, for, while, case, catch, &&, ||, ?:
|
|
13
|
+
*/
|
|
14
|
+
function calculateComplexity(node) {
|
|
15
|
+
let complexity = 1; // Base complexity
|
|
16
|
+
|
|
17
|
+
function visit(n) {
|
|
18
|
+
switch (n.kind) {
|
|
19
|
+
case ts.SyntaxKind.IfStatement:
|
|
20
|
+
case ts.SyntaxKind.ForStatement:
|
|
21
|
+
case ts.SyntaxKind.ForInStatement:
|
|
22
|
+
case ts.SyntaxKind.ForOfStatement:
|
|
23
|
+
case ts.SyntaxKind.WhileStatement:
|
|
24
|
+
case ts.SyntaxKind.DoStatement:
|
|
25
|
+
case ts.SyntaxKind.CaseClause:
|
|
26
|
+
case ts.SyntaxKind.CatchClause:
|
|
27
|
+
case ts.SyntaxKind.ConditionalExpression: // ternary ?:
|
|
28
|
+
complexity++;
|
|
29
|
+
break;
|
|
30
|
+
case ts.SyntaxKind.BinaryExpression:
|
|
31
|
+
const op = n.operatorToken.kind;
|
|
32
|
+
if (
|
|
33
|
+
op === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
34
|
+
op === ts.SyntaxKind.BarBarToken ||
|
|
35
|
+
op === ts.SyntaxKind.QuestionQuestionToken
|
|
36
|
+
) {
|
|
37
|
+
complexity++;
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
ts.forEachChild(n, visit);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
ts.forEachChild(node, visit);
|
|
45
|
+
return complexity;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract imports from source file
|
|
50
|
+
*/
|
|
51
|
+
function extractImports(sourceFile) {
|
|
52
|
+
const imports = [];
|
|
53
|
+
|
|
54
|
+
ts.forEachChild(sourceFile, (node) => {
|
|
55
|
+
if (ts.isImportDeclaration(node)) {
|
|
56
|
+
const moduleSpecifier = node.moduleSpecifier.text;
|
|
57
|
+
const importClause = node.importClause;
|
|
58
|
+
|
|
59
|
+
const importInfo = {
|
|
60
|
+
module: moduleSpecifier,
|
|
61
|
+
default: null,
|
|
62
|
+
named: [],
|
|
63
|
+
namespace: null,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (importClause) {
|
|
67
|
+
// Default import
|
|
68
|
+
if (importClause.name) {
|
|
69
|
+
importInfo.default = importClause.name.text;
|
|
70
|
+
}
|
|
71
|
+
// Named imports or namespace
|
|
72
|
+
if (importClause.namedBindings) {
|
|
73
|
+
if (ts.isNamespaceImport(importClause.namedBindings)) {
|
|
74
|
+
importInfo.namespace = importClause.namedBindings.name.text;
|
|
75
|
+
} else if (ts.isNamedImports(importClause.namedBindings)) {
|
|
76
|
+
importClause.namedBindings.elements.forEach((el) => {
|
|
77
|
+
importInfo.named.push(el.name.text);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
imports.push(importInfo);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return imports;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extract dependencies used within a function body
|
|
92
|
+
*/
|
|
93
|
+
function extractDependencies(node, imports) {
|
|
94
|
+
const deps = new Set();
|
|
95
|
+
const builtins = new Set([
|
|
96
|
+
"console",
|
|
97
|
+
"setTimeout",
|
|
98
|
+
"clearTimeout",
|
|
99
|
+
"setInterval",
|
|
100
|
+
"clearInterval",
|
|
101
|
+
"Promise",
|
|
102
|
+
"Array",
|
|
103
|
+
"Object",
|
|
104
|
+
"String",
|
|
105
|
+
"Number",
|
|
106
|
+
"Boolean",
|
|
107
|
+
"Date",
|
|
108
|
+
"Math",
|
|
109
|
+
"JSON",
|
|
110
|
+
"Error",
|
|
111
|
+
"Map",
|
|
112
|
+
"Set",
|
|
113
|
+
"WeakMap",
|
|
114
|
+
"WeakSet",
|
|
115
|
+
"Symbol",
|
|
116
|
+
"Proxy",
|
|
117
|
+
"Reflect",
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const importedNames = new Set();
|
|
121
|
+
imports.forEach((imp) => {
|
|
122
|
+
if (imp.default) importedNames.add(imp.default);
|
|
123
|
+
if (imp.namespace) importedNames.add(imp.namespace);
|
|
124
|
+
imp.named.forEach((n) => importedNames.add(n));
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
function visit(n) {
|
|
128
|
+
if (ts.isIdentifier(n)) {
|
|
129
|
+
const name = n.text;
|
|
130
|
+
if (importedNames.has(name)) {
|
|
131
|
+
deps.add(name);
|
|
132
|
+
} else if (builtins.has(name)) {
|
|
133
|
+
deps.add(`[builtin] ${name}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
ts.forEachChild(n, visit);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
ts.forEachChild(node, visit);
|
|
140
|
+
return Array.from(deps);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Detect patterns and edge case handling
|
|
145
|
+
*/
|
|
146
|
+
function detectPatterns(node) {
|
|
147
|
+
const patterns = [];
|
|
148
|
+
|
|
149
|
+
function visit(n) {
|
|
150
|
+
// Try-catch
|
|
151
|
+
if (ts.isTryStatement(n)) {
|
|
152
|
+
patterns.push("error-handling");
|
|
153
|
+
}
|
|
154
|
+
// Null checks
|
|
155
|
+
if (ts.isBinaryExpression(n)) {
|
|
156
|
+
const op = n.operatorToken.kind;
|
|
157
|
+
if (
|
|
158
|
+
op === ts.SyntaxKind.EqualsEqualsEqualsToken ||
|
|
159
|
+
op === ts.SyntaxKind.ExclamationEqualsEqualsToken
|
|
160
|
+
) {
|
|
161
|
+
const left = n.left.getText ? n.left.getText() : "";
|
|
162
|
+
const right = n.right.getText ? n.right.getText() : "";
|
|
163
|
+
if (
|
|
164
|
+
left === "null" ||
|
|
165
|
+
right === "null" ||
|
|
166
|
+
left === "undefined" ||
|
|
167
|
+
right === "undefined"
|
|
168
|
+
) {
|
|
169
|
+
patterns.push("null-check");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Optional chaining check via ??
|
|
173
|
+
if (op === ts.SyntaxKind.QuestionQuestionToken) {
|
|
174
|
+
patterns.push("nullish-coalescing");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Optional chaining
|
|
178
|
+
if (
|
|
179
|
+
n.kind === ts.SyntaxKind.PropertyAccessExpression &&
|
|
180
|
+
n.questionDotToken
|
|
181
|
+
) {
|
|
182
|
+
patterns.push("optional-chaining");
|
|
183
|
+
}
|
|
184
|
+
// Type guards
|
|
185
|
+
if (
|
|
186
|
+
ts.isTypeOfExpression(n) ||
|
|
187
|
+
(ts.isCallExpression(n) && n.expression.getText?.() === "typeof")
|
|
188
|
+
) {
|
|
189
|
+
patterns.push("type-guard");
|
|
190
|
+
}
|
|
191
|
+
// Async/await
|
|
192
|
+
if (ts.isAwaitExpression(n)) {
|
|
193
|
+
patterns.push("async-await");
|
|
194
|
+
}
|
|
195
|
+
// Generators
|
|
196
|
+
if (ts.isYieldExpression(n)) {
|
|
197
|
+
patterns.push("generator");
|
|
198
|
+
}
|
|
199
|
+
// Closures (arrow functions or function expressions inside)
|
|
200
|
+
if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) {
|
|
201
|
+
patterns.push("closure");
|
|
202
|
+
}
|
|
203
|
+
// Recursion detection (function calling itself)
|
|
204
|
+
if (ts.isCallExpression(n) && ts.isIdentifier(n.expression)) {
|
|
205
|
+
// Will be checked later with function name context
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
ts.forEachChild(n, visit);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
ts.forEachChild(node, visit);
|
|
212
|
+
return [...new Set(patterns)]; // Dedupe
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Get function body as string (truncated)
|
|
217
|
+
*/
|
|
218
|
+
function getFunctionBody(node, sourceFile, maxLines = 15) {
|
|
219
|
+
if (!node.body) return null;
|
|
220
|
+
|
|
221
|
+
const bodyText = node.body.getText(sourceFile);
|
|
222
|
+
const lines = bodyText.split("\n");
|
|
223
|
+
|
|
224
|
+
if (lines.length <= maxLines) {
|
|
225
|
+
return bodyText;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return (
|
|
229
|
+
lines.slice(0, maxLines).join("\n") +
|
|
230
|
+
`\n... (${lines.length - maxLines} more lines)`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Count lines of code in a node
|
|
236
|
+
*/
|
|
237
|
+
function countLines(node, sourceFile) {
|
|
238
|
+
const text = node.getText(sourceFile);
|
|
239
|
+
return text.split("\n").length;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Parse source file and extract detailed analysis
|
|
244
|
+
*/
|
|
245
|
+
export function parseSourceFile(filePath, options = {}) {
|
|
246
|
+
const { filter, maxBodyLines = 15, includeBody = true } = options;
|
|
247
|
+
|
|
248
|
+
if (!fs.existsSync(filePath)) {
|
|
249
|
+
return { error: `File not found: ${filePath}`, functions: {} };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
253
|
+
const sourceFile = ts.createSourceFile(
|
|
254
|
+
path.basename(filePath),
|
|
255
|
+
content,
|
|
256
|
+
ts.ScriptTarget.Latest,
|
|
257
|
+
true,
|
|
258
|
+
filePath.endsWith(".tsx") || filePath.endsWith(".jsx")
|
|
259
|
+
? ts.ScriptKind.TSX
|
|
260
|
+
: filePath.endsWith(".ts")
|
|
261
|
+
? ts.ScriptKind.TS
|
|
262
|
+
: ts.ScriptKind.JS,
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const imports = extractImports(sourceFile);
|
|
266
|
+
const functions = {};
|
|
267
|
+
|
|
268
|
+
function shouldInclude(name) {
|
|
269
|
+
if (!filter) return true;
|
|
270
|
+
return name.toLowerCase().includes(filter.toLowerCase());
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function analyzeFunction(node, name, isExported, isAsync) {
|
|
274
|
+
if (!shouldInclude(name)) return;
|
|
275
|
+
|
|
276
|
+
const complexity = calculateComplexity(node);
|
|
277
|
+
const lines = countLines(node, sourceFile);
|
|
278
|
+
const deps = extractDependencies(node, imports);
|
|
279
|
+
const patterns = detectPatterns(node);
|
|
280
|
+
const body = includeBody
|
|
281
|
+
? getFunctionBody(node, sourceFile, maxBodyLines)
|
|
282
|
+
: null;
|
|
283
|
+
|
|
284
|
+
// Get parameters
|
|
285
|
+
const params = node.parameters
|
|
286
|
+
? node.parameters.map((p) => {
|
|
287
|
+
const paramName = p.name.getText(sourceFile);
|
|
288
|
+
const paramType = p.type ? p.type.getText(sourceFile) : "any";
|
|
289
|
+
const optional = !!p.questionToken;
|
|
290
|
+
const defaultValue = p.initializer
|
|
291
|
+
? p.initializer.getText(sourceFile)
|
|
292
|
+
: null;
|
|
293
|
+
return {
|
|
294
|
+
name: paramName,
|
|
295
|
+
type: paramType,
|
|
296
|
+
optional,
|
|
297
|
+
default: defaultValue,
|
|
298
|
+
};
|
|
299
|
+
})
|
|
300
|
+
: [];
|
|
301
|
+
|
|
302
|
+
// Get return type
|
|
303
|
+
const returnType = node.type ? node.type.getText(sourceFile) : "inferred";
|
|
304
|
+
|
|
305
|
+
functions[name] = {
|
|
306
|
+
exported: isExported,
|
|
307
|
+
async: isAsync,
|
|
308
|
+
params,
|
|
309
|
+
returnType,
|
|
310
|
+
complexity,
|
|
311
|
+
lines,
|
|
312
|
+
dependencies: deps,
|
|
313
|
+
patterns,
|
|
314
|
+
...(body && { body }),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function visit(node) {
|
|
319
|
+
// Function declarations
|
|
320
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
321
|
+
const name = node.name.text;
|
|
322
|
+
const isExported = node.modifiers?.some(
|
|
323
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword,
|
|
324
|
+
);
|
|
325
|
+
const isAsync = node.modifiers?.some(
|
|
326
|
+
(m) => m.kind === ts.SyntaxKind.AsyncKeyword,
|
|
327
|
+
);
|
|
328
|
+
analyzeFunction(node, name, isExported, isAsync);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Arrow functions assigned to variables
|
|
332
|
+
if (ts.isVariableStatement(node)) {
|
|
333
|
+
const isExported = node.modifiers?.some(
|
|
334
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
node.declarationList.declarations.forEach((decl) => {
|
|
338
|
+
if (decl.initializer && ts.isArrowFunction(decl.initializer)) {
|
|
339
|
+
const name = decl.name.getText(sourceFile);
|
|
340
|
+
const isAsync = decl.initializer.modifiers?.some(
|
|
341
|
+
(m) => m.kind === ts.SyntaxKind.AsyncKeyword,
|
|
342
|
+
);
|
|
343
|
+
analyzeFunction(decl.initializer, name, isExported, isAsync);
|
|
344
|
+
}
|
|
345
|
+
// Function expressions
|
|
346
|
+
if (decl.initializer && ts.isFunctionExpression(decl.initializer)) {
|
|
347
|
+
const name = decl.name.getText(sourceFile);
|
|
348
|
+
const isAsync = decl.initializer.modifiers?.some(
|
|
349
|
+
(m) => m.kind === ts.SyntaxKind.AsyncKeyword,
|
|
350
|
+
);
|
|
351
|
+
analyzeFunction(decl.initializer, name, isExported, isAsync);
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Class methods
|
|
357
|
+
if (ts.isClassDeclaration(node) && node.name) {
|
|
358
|
+
const className = node.name.text;
|
|
359
|
+
const isClassExported = node.modifiers?.some(
|
|
360
|
+
(m) => m.kind === ts.SyntaxKind.ExportKeyword,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
node.members.forEach((member) => {
|
|
364
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
365
|
+
const methodName = `${className}.${member.name.getText(sourceFile)}`;
|
|
366
|
+
const isAsync = member.modifiers?.some(
|
|
367
|
+
(m) => m.kind === ts.SyntaxKind.AsyncKeyword,
|
|
368
|
+
);
|
|
369
|
+
analyzeFunction(member, methodName, isClassExported, isAsync);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
ts.forEachChild(node, visit);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
visit(sourceFile);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
file: filePath,
|
|
381
|
+
imports,
|
|
382
|
+
functions,
|
|
383
|
+
totalFunctions: Object.keys(functions).length,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Find source files for a package
|
|
389
|
+
*/
|
|
390
|
+
export function findSourceFiles(pkgDir, options = {}) {
|
|
391
|
+
const {
|
|
392
|
+
maxFiles = 10,
|
|
393
|
+
include = [
|
|
394
|
+
"src/**/*.ts",
|
|
395
|
+
"src/**/*.js",
|
|
396
|
+
"lib/**/*.ts",
|
|
397
|
+
"lib/**/*.js",
|
|
398
|
+
"dist/**/*.js",
|
|
399
|
+
"*.ts",
|
|
400
|
+
"*.js",
|
|
401
|
+
],
|
|
402
|
+
} = options;
|
|
403
|
+
|
|
404
|
+
const sourceFiles = [];
|
|
405
|
+
|
|
406
|
+
for (const pattern of include) {
|
|
407
|
+
const fullPattern = path.join(pkgDir, pattern);
|
|
408
|
+
// Simple glob-like matching
|
|
409
|
+
const baseDir = path.dirname(fullPattern.split("*")[0]);
|
|
410
|
+
if (!fs.existsSync(baseDir)) continue;
|
|
411
|
+
|
|
412
|
+
function walkDir(dir, depth = 0) {
|
|
413
|
+
if (depth > 5 || sourceFiles.length >= maxFiles) return;
|
|
414
|
+
|
|
415
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
416
|
+
for (const entry of entries) {
|
|
417
|
+
if (sourceFiles.length >= maxFiles) break;
|
|
418
|
+
|
|
419
|
+
const fullPath = path.join(dir, entry.name);
|
|
420
|
+
if (
|
|
421
|
+
entry.isDirectory() &&
|
|
422
|
+
!entry.name.startsWith(".") &&
|
|
423
|
+
entry.name !== "node_modules"
|
|
424
|
+
) {
|
|
425
|
+
walkDir(fullPath, depth + 1);
|
|
426
|
+
} else if (
|
|
427
|
+
entry.isFile() &&
|
|
428
|
+
(entry.name.endsWith(".ts") ||
|
|
429
|
+
entry.name.endsWith(".tsx") ||
|
|
430
|
+
entry.name.endsWith(".js") ||
|
|
431
|
+
entry.name.endsWith(".jsx"))
|
|
432
|
+
) {
|
|
433
|
+
// Skip declaration files
|
|
434
|
+
if (!entry.name.endsWith(".d.ts")) {
|
|
435
|
+
sourceFiles.push(fullPath);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (fs.existsSync(baseDir)) {
|
|
442
|
+
walkDir(baseDir);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return sourceFiles;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Analyze a package's source code
|
|
451
|
+
*/
|
|
452
|
+
export function analyzePackageSource(pkgDir, options = {}) {
|
|
453
|
+
const {
|
|
454
|
+
filter,
|
|
455
|
+
maxFiles = 5,
|
|
456
|
+
maxBodyLines = 10,
|
|
457
|
+
includeBody = false,
|
|
458
|
+
} = options;
|
|
459
|
+
|
|
460
|
+
const sourceFiles = findSourceFiles(pkgDir, { maxFiles });
|
|
461
|
+
|
|
462
|
+
if (sourceFiles.length === 0) {
|
|
463
|
+
return { error: "No source files found", files: [] };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const results = {
|
|
467
|
+
files: [],
|
|
468
|
+
summary: {
|
|
469
|
+
totalFiles: sourceFiles.length,
|
|
470
|
+
totalFunctions: 0,
|
|
471
|
+
avgComplexity: 0,
|
|
472
|
+
highComplexityFunctions: [],
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
let totalComplexity = 0;
|
|
477
|
+
let functionCount = 0;
|
|
478
|
+
|
|
479
|
+
for (const file of sourceFiles) {
|
|
480
|
+
const analysis = parseSourceFile(file, {
|
|
481
|
+
filter,
|
|
482
|
+
maxBodyLines,
|
|
483
|
+
includeBody,
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
if (analysis.error) continue;
|
|
487
|
+
|
|
488
|
+
results.files.push({
|
|
489
|
+
path: path.relative(pkgDir, file),
|
|
490
|
+
functions: analysis.functions,
|
|
491
|
+
imports: analysis.imports,
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
for (const [name, info] of Object.entries(analysis.functions)) {
|
|
495
|
+
functionCount++;
|
|
496
|
+
totalComplexity += info.complexity;
|
|
497
|
+
|
|
498
|
+
if (info.complexity >= 10) {
|
|
499
|
+
results.summary.highComplexityFunctions.push({
|
|
500
|
+
name,
|
|
501
|
+
file: path.relative(pkgDir, file),
|
|
502
|
+
complexity: info.complexity,
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
results.summary.totalFunctions = functionCount;
|
|
509
|
+
results.summary.avgComplexity =
|
|
510
|
+
functionCount > 0
|
|
511
|
+
? Math.round((totalComplexity / functionCount) * 10) / 10
|
|
512
|
+
: 0;
|
|
513
|
+
results.summary.highComplexityFunctions.sort(
|
|
514
|
+
(a, b) => b.complexity - a.complexity,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
return results;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export default {
|
|
521
|
+
parseSourceFile,
|
|
522
|
+
findSourceFiles,
|
|
523
|
+
analyzePackageSource,
|
|
524
|
+
};
|