@jonathan0823/pi-sonarqube 0.3.2 → 0.5.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/api.d.ts.map +1 -1
- package/dist/api.js +48 -13
- package/dist/api.js.map +1 -1
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +66 -19
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +13 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +148 -31
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +26 -4
- package/dist/types.js.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +41 -11
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/src/api.ts +212 -58
- package/src/commands.ts +155 -53
- package/src/config.ts +84 -23
- package/src/index.ts +437 -86
- package/src/types.ts +27 -4
- package/src/ui.ts +121 -26
package/src/types.ts
CHANGED
|
@@ -41,6 +41,7 @@ export interface FileDuplication {
|
|
|
41
41
|
filePath: string;
|
|
42
42
|
fileKey: string;
|
|
43
43
|
duplicatedLines: number;
|
|
44
|
+
duplicatedLinesDensity: number;
|
|
44
45
|
duplicatedBlocks: number;
|
|
45
46
|
}
|
|
46
47
|
|
|
@@ -119,8 +120,30 @@ export interface InitCommandOptions {
|
|
|
119
120
|
|
|
120
121
|
// ── Constants shared by adapter and extension ───────────────────────────────
|
|
121
122
|
|
|
122
|
-
export const SONAR_SEVERITIES = [
|
|
123
|
-
|
|
123
|
+
export const SONAR_SEVERITIES = [
|
|
124
|
+
"BLOCKER",
|
|
125
|
+
"CRITICAL",
|
|
126
|
+
"MAJOR",
|
|
127
|
+
"MINOR",
|
|
128
|
+
"INFO",
|
|
129
|
+
] as const;
|
|
130
|
+
export const SONAR_STATUSES = [
|
|
131
|
+
"OPEN",
|
|
132
|
+
"CONFIRMED",
|
|
133
|
+
"REOPENED",
|
|
134
|
+
"RESOLVED",
|
|
135
|
+
"CLOSED",
|
|
136
|
+
] as const;
|
|
124
137
|
export const SONAR_TYPES = ["BUG", "VULNERABILITY", "CODE_SMELL"] as const;
|
|
125
|
-
export const SONAR_SOFTWARE_QUALITIES = [
|
|
126
|
-
|
|
138
|
+
export const SONAR_SOFTWARE_QUALITIES = [
|
|
139
|
+
"MAINTAINABILITY",
|
|
140
|
+
"RELIABILITY",
|
|
141
|
+
"SECURITY",
|
|
142
|
+
] as const;
|
|
143
|
+
export const SONAR_IMPACT_SEVERITIES = [
|
|
144
|
+
"BLOCKER",
|
|
145
|
+
"HIGH",
|
|
146
|
+
"MEDIUM",
|
|
147
|
+
"LOW",
|
|
148
|
+
"INFO",
|
|
149
|
+
] as const;
|
package/src/ui.ts
CHANGED
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Theme,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
ExtensionCommandContext,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent";
|
|
2
6
|
import { Key, matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
7
|
import { readFile } from "node:fs/promises";
|
|
4
8
|
import { resolve } from "node:path";
|
|
5
|
-
import type {
|
|
6
|
-
|
|
9
|
+
import type {
|
|
10
|
+
SonarIssue,
|
|
11
|
+
SonarAnalysisState,
|
|
12
|
+
SonarIssueFetchOptions,
|
|
13
|
+
FileDuplication,
|
|
14
|
+
DuplicationBlockGroup,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
import {
|
|
17
|
+
issueFilterLabel,
|
|
18
|
+
fetchIssues,
|
|
19
|
+
createAnalysisState,
|
|
20
|
+
normalizeIssueFilters,
|
|
21
|
+
fetchCleanCodeMode,
|
|
22
|
+
} from "./api.js";
|
|
7
23
|
import { resolveConfig, resolveTarget } from "./config.js";
|
|
8
24
|
|
|
9
25
|
// ── Issue preview ───────────────────────────────────────────────────────────
|
|
10
26
|
|
|
11
|
-
export async function buildIssuePreview(
|
|
27
|
+
export async function buildIssuePreview(
|
|
28
|
+
baseDir: string,
|
|
29
|
+
issue: SonarIssue,
|
|
30
|
+
radius = 3,
|
|
31
|
+
): Promise<string> {
|
|
12
32
|
const filePath = resolve(baseDir, issue.filePath);
|
|
13
33
|
const content = await readFile(filePath, "utf8");
|
|
14
34
|
const lines = content.split(/\r?\n/);
|
|
@@ -40,7 +60,11 @@ export async function buildDuplicationPreview(
|
|
|
40
60
|
cache.set(absolutePath, content);
|
|
41
61
|
return content.split(/\r?\n/);
|
|
42
62
|
};
|
|
43
|
-
const renderRange = (
|
|
63
|
+
const renderRange = (
|
|
64
|
+
lines: string[],
|
|
65
|
+
from: number,
|
|
66
|
+
size: number,
|
|
67
|
+
): string[] => {
|
|
44
68
|
const start = Math.max(1, from);
|
|
45
69
|
const end = Math.min(lines.length, from + size - 1);
|
|
46
70
|
const width = String(end).length;
|
|
@@ -76,7 +100,18 @@ export async function buildDuplicationPreview(
|
|
|
76
100
|
|
|
77
101
|
// ── Analysis UI ─────────────────────────────────────────────────────────────
|
|
78
102
|
|
|
79
|
-
const ANALYSIS_SPINNER = [
|
|
103
|
+
const ANALYSIS_SPINNER = [
|
|
104
|
+
"⠋",
|
|
105
|
+
"⠙",
|
|
106
|
+
"⠹",
|
|
107
|
+
"⠸",
|
|
108
|
+
"⠼",
|
|
109
|
+
"⠴",
|
|
110
|
+
"⠦",
|
|
111
|
+
"⠧",
|
|
112
|
+
"⠇",
|
|
113
|
+
"⠏",
|
|
114
|
+
] as const;
|
|
80
115
|
const ANALYSIS_WIDGET_KEY = "sonarqube-analysis";
|
|
81
116
|
|
|
82
117
|
export interface AnalysisUiHandle {
|
|
@@ -84,16 +119,25 @@ export interface AnalysisUiHandle {
|
|
|
84
119
|
stop(): void;
|
|
85
120
|
}
|
|
86
121
|
|
|
87
|
-
export function startAnalysisUi(
|
|
122
|
+
export function startAnalysisUi(
|
|
123
|
+
ctx: ExtensionContext,
|
|
124
|
+
projectKey: string,
|
|
125
|
+
): AnalysisUiHandle {
|
|
88
126
|
let frameIndex = 0;
|
|
89
127
|
let phase = "Starting...";
|
|
90
128
|
const render = () => {
|
|
91
129
|
const frame = ANALYSIS_SPINNER[frameIndex];
|
|
92
|
-
ctx.ui.setWidget(ANALYSIS_WIDGET_KEY, [
|
|
130
|
+
ctx.ui.setWidget(ANALYSIS_WIDGET_KEY, [
|
|
131
|
+
`${frame} SonarQube ${projectKey}`,
|
|
132
|
+
phase,
|
|
133
|
+
]);
|
|
93
134
|
};
|
|
94
135
|
|
|
95
136
|
ctx.ui.setWorkingMessage(`Analyzing ${projectKey}...`);
|
|
96
|
-
ctx.ui.setWorkingIndicator({
|
|
137
|
+
ctx.ui.setWorkingIndicator({
|
|
138
|
+
frames: ANALYSIS_SPINNER as unknown as string[],
|
|
139
|
+
intervalMs: 80,
|
|
140
|
+
});
|
|
97
141
|
render();
|
|
98
142
|
|
|
99
143
|
const timer = setInterval(() => {
|
|
@@ -129,11 +173,18 @@ export class IssueBrowser {
|
|
|
129
173
|
invalidate(): void {} // NOSONAR - required by TUI interface
|
|
130
174
|
|
|
131
175
|
handleInput(data: string): void {
|
|
132
|
-
this.selected = handleListBrowserInput(
|
|
176
|
+
this.selected = handleListBrowserInput(
|
|
177
|
+
data,
|
|
178
|
+
this.selected,
|
|
179
|
+
this.state.issues.length,
|
|
180
|
+
this.done,
|
|
181
|
+
);
|
|
133
182
|
}
|
|
134
183
|
|
|
135
184
|
render(width: number): string[] {
|
|
136
|
-
const filterSuffix = this.state.filters
|
|
185
|
+
const filterSuffix = this.state.filters
|
|
186
|
+
? ` • ${issueFilterLabel(this.state.filters)}`
|
|
187
|
+
: "";
|
|
137
188
|
return renderListBrowser(this.theme, width, {
|
|
138
189
|
title: "SonarQube Issues",
|
|
139
190
|
subtitle: `${this.state.projectKey} • ${this.state.totalIssues} issue(s)${filterSuffix}`,
|
|
@@ -143,9 +194,15 @@ export class IssueBrowser {
|
|
|
143
194
|
emptyMessage: "No open issues found.",
|
|
144
195
|
footer: "Up/Down to move, Enter to preview, Esc to close",
|
|
145
196
|
renderItem: (issue, issueIndex, isSelected) => {
|
|
146
|
-
const marker = isSelected
|
|
147
|
-
|
|
148
|
-
|
|
197
|
+
const marker = isSelected
|
|
198
|
+
? this.theme.fg("accent", ">")
|
|
199
|
+
: this.theme.fg("dim", " ");
|
|
200
|
+
const location = issue.line
|
|
201
|
+
? `${issue.filePath}:${issue.line}`
|
|
202
|
+
: issue.filePath;
|
|
203
|
+
const rule = issue.ruleName
|
|
204
|
+
? `${issue.rule} (${issue.ruleName})`
|
|
205
|
+
: issue.rule;
|
|
149
206
|
const severity = severityColor(this.theme, issue.severity);
|
|
150
207
|
return [
|
|
151
208
|
marker,
|
|
@@ -199,7 +256,11 @@ function handleListBrowserInput(
|
|
|
199
256
|
return selected;
|
|
200
257
|
}
|
|
201
258
|
|
|
202
|
-
function getBrowserWindow(
|
|
259
|
+
function getBrowserWindow(
|
|
260
|
+
selected: number,
|
|
261
|
+
total: number,
|
|
262
|
+
pageSize: number,
|
|
263
|
+
): { start: number; end: number } {
|
|
203
264
|
const halfWindow = Math.floor(pageSize / 2);
|
|
204
265
|
const maxStart = Math.max(0, total - pageSize);
|
|
205
266
|
const start = Math.max(0, Math.min(selected - halfWindow, maxStart));
|
|
@@ -230,7 +291,11 @@ function renderListBrowser<T>(
|
|
|
230
291
|
];
|
|
231
292
|
|
|
232
293
|
const pageSize = Math.min(config.pageSize, config.items.length);
|
|
233
|
-
const { start, end } = getBrowserWindow(
|
|
294
|
+
const { start, end } = getBrowserWindow(
|
|
295
|
+
config.selected,
|
|
296
|
+
config.items.length,
|
|
297
|
+
pageSize,
|
|
298
|
+
);
|
|
234
299
|
const visible = config.items.slice(start, end);
|
|
235
300
|
|
|
236
301
|
if (visible.length === 0) {
|
|
@@ -243,14 +308,26 @@ function renderListBrowser<T>(
|
|
|
243
308
|
}
|
|
244
309
|
|
|
245
310
|
if (start > 0) {
|
|
246
|
-
lines.push(
|
|
311
|
+
lines.push(
|
|
312
|
+
truncateToWidth(theme.fg("dim", `... ${start} more above`), width),
|
|
313
|
+
);
|
|
247
314
|
}
|
|
248
315
|
for (const [offset, item] of visible.entries()) {
|
|
249
316
|
const index = start + offset;
|
|
250
|
-
lines.push(
|
|
317
|
+
lines.push(
|
|
318
|
+
truncateToWidth(
|
|
319
|
+
config.renderItem(item, index, index === config.selected),
|
|
320
|
+
width,
|
|
321
|
+
),
|
|
322
|
+
);
|
|
251
323
|
}
|
|
252
324
|
if (end < config.items.length) {
|
|
253
|
-
lines.push(
|
|
325
|
+
lines.push(
|
|
326
|
+
truncateToWidth(
|
|
327
|
+
theme.fg("dim", `... ${config.items.length - end} more below`),
|
|
328
|
+
width,
|
|
329
|
+
),
|
|
330
|
+
);
|
|
254
331
|
}
|
|
255
332
|
|
|
256
333
|
lines.push("", truncateToWidth(theme.fg("dim", config.footer), width));
|
|
@@ -271,7 +348,12 @@ export class DuplicationBrowser {
|
|
|
271
348
|
invalidate(): void {} // NOSONAR - required by TUI interface
|
|
272
349
|
|
|
273
350
|
handleInput(data: string): void {
|
|
274
|
-
this.selected = handleListBrowserInput(
|
|
351
|
+
this.selected = handleListBrowserInput(
|
|
352
|
+
data,
|
|
353
|
+
this.selected,
|
|
354
|
+
this.files.length,
|
|
355
|
+
this.done,
|
|
356
|
+
);
|
|
275
357
|
}
|
|
276
358
|
|
|
277
359
|
render(width: number): string[] {
|
|
@@ -284,8 +366,10 @@ export class DuplicationBrowser {
|
|
|
284
366
|
emptyMessage: "No duplicate code found.",
|
|
285
367
|
footer: "Up/Down to move, Enter for details, Esc to close",
|
|
286
368
|
renderItem: (file, index, isSelected) => {
|
|
287
|
-
const marker = isSelected
|
|
288
|
-
|
|
369
|
+
const marker = isSelected
|
|
370
|
+
? this.theme.fg("accent", ">")
|
|
371
|
+
: this.theme.fg("dim", " ");
|
|
372
|
+
const detail = `dup%=${file.duplicatedLinesDensity.toFixed(1)} blocks=${file.duplicatedBlocks} lines=${file.duplicatedLines}`;
|
|
289
373
|
return `${marker} ${String(index + 1).padStart(2, " ")}. ${this.theme.fg("accent", file.filePath)} ${this.theme.fg("dim", detail)}`;
|
|
290
374
|
},
|
|
291
375
|
});
|
|
@@ -297,7 +381,9 @@ export async function showDuplicationBrowser(
|
|
|
297
381
|
files: FileDuplication[],
|
|
298
382
|
): Promise<number | null> {
|
|
299
383
|
if (ctx.mode !== "tui" || files.length === 0) return null;
|
|
300
|
-
return await ctx.ui.custom<number | null>(
|
|
384
|
+
return await ctx.ui.custom<number | null>(
|
|
385
|
+
(_tui, theme, _kb, done) => new DuplicationBrowser(files, theme, done),
|
|
386
|
+
);
|
|
301
387
|
}
|
|
302
388
|
|
|
303
389
|
// ── Issue browser / preview helpers (used by index.ts) ──────────────────────
|
|
@@ -308,7 +394,9 @@ export async function showIssueBrowser(
|
|
|
308
394
|
): Promise<number | null> {
|
|
309
395
|
if (ctx.mode !== "tui") return null;
|
|
310
396
|
if (state.issues.length === 0) return null;
|
|
311
|
-
return await ctx.ui.custom<number | null>(
|
|
397
|
+
return await ctx.ui.custom<number | null>(
|
|
398
|
+
(_tui, theme, _kb, done) => new IssueBrowser(state, theme, done),
|
|
399
|
+
);
|
|
312
400
|
}
|
|
313
401
|
|
|
314
402
|
export async function openIssuePreview(
|
|
@@ -341,8 +429,15 @@ export async function loadProjectIssuesFromApi(
|
|
|
341
429
|
ctx.signal,
|
|
342
430
|
normalizedFilters,
|
|
343
431
|
);
|
|
344
|
-
const cleanCodeMode = await fetchCleanCodeMode(
|
|
345
|
-
|
|
432
|
+
const cleanCodeMode = await fetchCleanCodeMode(
|
|
433
|
+
config.serverUrl,
|
|
434
|
+
config.token,
|
|
435
|
+
ctx.signal,
|
|
436
|
+
);
|
|
437
|
+
return createAnalysisState(config, issues, {
|
|
438
|
+
filters: normalizedFilters,
|
|
439
|
+
cleanCodeMode,
|
|
440
|
+
});
|
|
346
441
|
}
|
|
347
442
|
|
|
348
443
|
export async function resolveTargetState(
|