universal-ast-mapper 1.28.0 → 2.0.1

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/fix.js ADDED
@@ -0,0 +1,92 @@
1
+ // ─── Builder ──────────────────────────────────────────────────────────────────
2
+ export function buildFixSuggestions(opts) {
3
+ const suggestions = [];
4
+ // ── Dead exports → remove-dead-export (high confidence only) ─────────────
5
+ if (opts.dead) {
6
+ for (const dead of opts.dead) {
7
+ if (dead.confidence !== "high")
8
+ continue;
9
+ suggestions.push({
10
+ kind: "remove-dead-export",
11
+ file: dead.file,
12
+ symbol: dead.symbol,
13
+ description: `"${dead.symbol}" is exported but never imported within the scanned directory. Remove the export keyword to reduce surface area.`,
14
+ before: `export ${dead.kind} ${dead.symbol}`,
15
+ after: `${dead.kind} ${dead.symbol}`,
16
+ priority: 2,
17
+ });
18
+ }
19
+ }
20
+ // ── Smells ────────────────────────────────────────────────────────────────
21
+ if (opts.smells) {
22
+ for (const smell of opts.smells) {
23
+ if (smell.smell === "long-method") {
24
+ suggestions.push({
25
+ kind: "extract-method",
26
+ file: smell.file,
27
+ line: smell.line,
28
+ symbol: smell.symbol,
29
+ description: smell.symbol
30
+ ? `"${smell.symbol}" is too long. ${smell.message}. Extract cohesive blocks into smaller helper functions.`
31
+ : `${smell.message}. Extract cohesive blocks into smaller helper functions.`,
32
+ priority: 3,
33
+ });
34
+ }
35
+ else if (smell.smell === "god-class") {
36
+ suggestions.push({
37
+ kind: "split-class",
38
+ file: smell.file,
39
+ line: smell.line,
40
+ symbol: smell.symbol,
41
+ description: smell.symbol
42
+ ? `"${smell.symbol}" has too many responsibilities. ${smell.message}. Consider splitting into focused classes.`
43
+ : `${smell.message}. Consider splitting into focused classes.`,
44
+ priority: 2,
45
+ });
46
+ }
47
+ }
48
+ }
49
+ // ── Security issues ───────────────────────────────────────────────────────
50
+ if (opts.security) {
51
+ for (const issue of opts.security) {
52
+ if (issue.rule === "eval") {
53
+ suggestions.push({
54
+ kind: "remove-eval",
55
+ file: issue.file,
56
+ line: issue.line,
57
+ description: `${issue.message}. Replace eval() with a safer alternative such as JSON.parse() for data, or Function() with strict input validation.`,
58
+ before: `eval(userInput)`,
59
+ after: `JSON.parse(userInput) // or use a safe parser`,
60
+ priority: 1,
61
+ });
62
+ }
63
+ else if (issue.rule === "http-url") {
64
+ // Try to extract the actual URL from the snippet for a more precise suggestion.
65
+ const urlMatch = issue.snippet.match(/http:\/\/[^\s'"`,)]+/);
66
+ const exampleUrl = urlMatch ? urlMatch[0] : "http://api.example.com";
67
+ const httpsUrl = exampleUrl.replace("http://", "https://");
68
+ suggestions.push({
69
+ kind: "use-https",
70
+ file: issue.file,
71
+ line: issue.line,
72
+ description: `${issue.message}. Switch to HTTPS to ensure data in transit is encrypted.`,
73
+ before: exampleUrl,
74
+ after: httpsUrl,
75
+ priority: 3,
76
+ });
77
+ }
78
+ else if (issue.rule === "no-rate-limit") {
79
+ suggestions.push({
80
+ kind: "add-rate-limit",
81
+ file: issue.file,
82
+ line: issue.line,
83
+ description: `${issue.message}. Add rate-limit middleware (e.g. express-rate-limit) to prevent abuse.`,
84
+ before: `app.post('/api/endpoint', handler)`,
85
+ after: `app.post('/api/endpoint', rateLimiter, handler)`,
86
+ priority: 1,
87
+ });
88
+ }
89
+ }
90
+ }
91
+ return suggestions;
92
+ }
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const HISTORY_SUBPATH = ".ast-map/history.json";
4
+ export function historyPath(root) {
5
+ return path.join(root, HISTORY_SUBPATH);
6
+ }
7
+ export function loadHistory(root) {
8
+ try {
9
+ return JSON.parse(fs.readFileSync(historyPath(root), "utf8"));
10
+ }
11
+ catch {
12
+ return [];
13
+ }
14
+ }
15
+ /** Append current report to history (one entry per calendar day, keep last 30). */
16
+ export function appendHistory(root, report) {
17
+ const entry = {
18
+ date: report.generatedAt,
19
+ score: report.score,
20
+ grade: report.grade,
21
+ files: report.fileCount,
22
+ symbols: report.symbolCount,
23
+ dead: report.dead.count,
24
+ cycles: report.cycles.count,
25
+ maxComplexity: report.complexity.max,
26
+ coverage: Math.round(report.testCoverage.coverageRatio * 100),
27
+ };
28
+ const history = loadHistory(root);
29
+ const today = entry.date.slice(0, 10);
30
+ const filtered = history.filter((h) => h.date.slice(0, 10) !== today);
31
+ const updated = [...filtered, entry].slice(-30);
32
+ const p = historyPath(root);
33
+ fs.mkdirSync(path.dirname(p), { recursive: true });
34
+ fs.writeFileSync(p, JSON.stringify(updated, null, 2), "utf8");
35
+ return updated;
36
+ }