@jonathan0823/pi-sonarqube 0.6.0 → 0.8.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/README.md +8 -8
- package/dist/api.d.ts +3 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -6
- package/dist/api.js.map +1 -1
- package/dist/commands.d.ts +3 -2
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +34 -2
- package/dist/commands.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +122 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -5
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/ui.d.ts +25 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +77 -9
- package/dist/ui.js.map +1 -1
- package/package.json +4 -1
- package/src/api.ts +24 -24
- package/src/commands.ts +64 -9
- package/src/config.ts +151 -1
- package/src/index.ts +43 -6
- package/src/types.ts +3 -1
- package/src/ui.ts +114 -11
package/src/api.ts
CHANGED
|
@@ -18,18 +18,19 @@ import {
|
|
|
18
18
|
SONAR_TYPES,
|
|
19
19
|
SONAR_SOFTWARE_QUALITIES,
|
|
20
20
|
SONAR_IMPACT_SEVERITIES,
|
|
21
|
+
CleanCodeMode,
|
|
21
22
|
} from "./types.js";
|
|
22
23
|
import { parseProperties } from "./config.js";
|
|
23
24
|
|
|
24
25
|
// ── Clean code mode detection ────────────────────────────────────────────────
|
|
25
26
|
|
|
26
|
-
const cleanCodeModeCache = new Map<string,
|
|
27
|
+
const cleanCodeModeCache = new Map<string, CleanCodeMode>();
|
|
27
28
|
|
|
28
29
|
export async function fetchCleanCodeMode(
|
|
29
30
|
serverUrl: string,
|
|
30
31
|
token?: string,
|
|
31
32
|
signal?: AbortSignal,
|
|
32
|
-
): Promise<
|
|
33
|
+
): Promise<CleanCodeMode | undefined> {
|
|
33
34
|
const cached = cleanCodeModeCache.get(serverUrl);
|
|
34
35
|
if (cached) return cached;
|
|
35
36
|
try {
|
|
@@ -262,8 +263,14 @@ function mergeFilters(
|
|
|
262
263
|
merged.statuses = mergeFilterField(merged.statuses, filter.statuses);
|
|
263
264
|
merged.types = mergeFilterField(merged.types, filter.types);
|
|
264
265
|
merged.rules = mergeFilterField(merged.rules, filter.rules);
|
|
265
|
-
merged.softwareQualities = mergeFilterField(
|
|
266
|
-
|
|
266
|
+
merged.softwareQualities = mergeFilterField(
|
|
267
|
+
merged.softwareQualities,
|
|
268
|
+
filter.softwareQualities,
|
|
269
|
+
);
|
|
270
|
+
merged.impactSeverities = mergeFilterField(
|
|
271
|
+
merged.impactSeverities,
|
|
272
|
+
filter.impactSeverities,
|
|
273
|
+
);
|
|
267
274
|
if (filter.pathScope) merged.pathScope = filter.pathScope;
|
|
268
275
|
}
|
|
269
276
|
return normalizeIssueFilters(merged);
|
|
@@ -523,10 +530,7 @@ function buildIssueSearchUrl(
|
|
|
523
530
|
filters.impactSeverities.join(","),
|
|
524
531
|
);
|
|
525
532
|
if (filters?.componentKeys?.length)
|
|
526
|
-
url.searchParams.set(
|
|
527
|
-
"componentKeys",
|
|
528
|
-
filters.componentKeys.join(","),
|
|
529
|
-
);
|
|
533
|
+
url.searchParams.set("componentKeys", filters.componentKeys.join(","));
|
|
530
534
|
return url.toString();
|
|
531
535
|
}
|
|
532
536
|
|
|
@@ -653,19 +657,19 @@ export async function fetchProjectProfiles(
|
|
|
653
657
|
token: string | undefined,
|
|
654
658
|
projectKey: string,
|
|
655
659
|
signal?: AbortSignal,
|
|
656
|
-
): Promise<Array<{key: string; language: string; name: string}>> {
|
|
660
|
+
): Promise<Array<{ key: string; language: string; name: string }>> {
|
|
657
661
|
try {
|
|
658
662
|
const result = await fetchJson<{
|
|
659
|
-
profiles: Array<{key?: string; language?: string; name?: string}>;
|
|
663
|
+
profiles: Array<{ key?: string; language?: string; name?: string }>;
|
|
660
664
|
}>(
|
|
661
665
|
`${serverUrl}/api/qualityprofiles/search?project=${encodeURIComponent(projectKey)}`,
|
|
662
666
|
token,
|
|
663
667
|
signal,
|
|
664
668
|
);
|
|
665
|
-
return (result.profiles ?? []).map(p => ({
|
|
666
|
-
key: p.key ??
|
|
667
|
-
language: p.language ??
|
|
668
|
-
name: p.name ??
|
|
669
|
+
return (result.profiles ?? []).map((p) => ({
|
|
670
|
+
key: p.key ?? "",
|
|
671
|
+
language: p.language ?? "",
|
|
672
|
+
name: p.name ?? "",
|
|
669
673
|
}));
|
|
670
674
|
} catch {
|
|
671
675
|
return [];
|
|
@@ -678,7 +682,7 @@ export async function fetchRuleSearch(
|
|
|
678
682
|
query: string,
|
|
679
683
|
languages?: string[],
|
|
680
684
|
signal?: AbortSignal,
|
|
681
|
-
): Promise<Array<{key: string; name?: string}>> {
|
|
685
|
+
): Promise<Array<{ key: string; name?: string }>> {
|
|
682
686
|
try {
|
|
683
687
|
const params = new URLSearchParams();
|
|
684
688
|
if (query) params.set("q", query);
|
|
@@ -687,14 +691,10 @@ export async function fetchRuleSearch(
|
|
|
687
691
|
if (languages?.length) params.set("languages", languages.join(","));
|
|
688
692
|
|
|
689
693
|
const result = await fetchJson<{
|
|
690
|
-
rules: Array<{key?: string; name?: string}>;
|
|
691
|
-
}>(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
signal,
|
|
695
|
-
);
|
|
696
|
-
return (result.rules ?? []).map(r => ({
|
|
697
|
-
key: r.key ?? '',
|
|
694
|
+
rules: Array<{ key?: string; name?: string }>;
|
|
695
|
+
}>(`${serverUrl}/api/rules/search?${params.toString()}`, token, signal);
|
|
696
|
+
return (result.rules ?? []).map((r) => ({
|
|
697
|
+
key: r.key ?? "",
|
|
698
698
|
name: r.name,
|
|
699
699
|
}));
|
|
700
700
|
} catch {
|
|
@@ -797,7 +797,7 @@ export async function fetchIssueSeverityCounts(
|
|
|
797
797
|
token: string | undefined,
|
|
798
798
|
projectKey: string,
|
|
799
799
|
signal?: AbortSignal,
|
|
800
|
-
mode?:
|
|
800
|
+
mode?: CleanCodeMode,
|
|
801
801
|
): Promise<IssueSeverityCounts | undefined> {
|
|
802
802
|
try {
|
|
803
803
|
const facetKey = mode === "MQR" ? "impactSeverities" : "severities";
|
package/src/commands.ts
CHANGED
|
@@ -17,11 +17,16 @@ import {
|
|
|
17
17
|
SONAR_TYPES,
|
|
18
18
|
SONAR_SOFTWARE_QUALITIES,
|
|
19
19
|
SONAR_IMPACT_SEVERITIES,
|
|
20
|
+
CleanCodeMode,
|
|
20
21
|
} from "./types.js";
|
|
21
22
|
import { execFileSync } from "node:child_process";
|
|
22
23
|
import { readdirSync } from "node:fs";
|
|
23
24
|
import { basename, dirname, relative, resolve } from "node:path";
|
|
24
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
looksLikePath,
|
|
27
|
+
loadWorkspaceRegistry,
|
|
28
|
+
knownTargets,
|
|
29
|
+
} from "./config.js";
|
|
25
30
|
import {
|
|
26
31
|
parseSonarIssueArgs,
|
|
27
32
|
issueFilterLabel,
|
|
@@ -118,9 +123,7 @@ export function splitSonarArgumentContext(argumentText: string): {
|
|
|
118
123
|
};
|
|
119
124
|
}
|
|
120
125
|
|
|
121
|
-
function createFilterCompletionList(
|
|
122
|
-
mode?: "STANDARD" | "MQR",
|
|
123
|
-
): AutocompleteItem[] {
|
|
126
|
+
function createFilterCompletionList(mode?: CleanCodeMode): AutocompleteItem[] {
|
|
124
127
|
const buildItems = (
|
|
125
128
|
groups: ReadonlyArray<
|
|
126
129
|
readonly [string, readonly string[], string, boolean]
|
|
@@ -370,7 +373,7 @@ function getInPathCompletions(
|
|
|
370
373
|
|
|
371
374
|
function getOpenCompletions(
|
|
372
375
|
issues: SonarIssue[] | undefined,
|
|
373
|
-
mode:
|
|
376
|
+
mode: CleanCodeMode | undefined,
|
|
374
377
|
current: string,
|
|
375
378
|
): AutocompleteItem[] | null {
|
|
376
379
|
const issueItems = (issues ?? []).slice(0, 10).map((issue, index) => {
|
|
@@ -388,7 +391,7 @@ function getOpenCompletions(
|
|
|
388
391
|
|
|
389
392
|
async function getFilterCompletions(
|
|
390
393
|
current: string,
|
|
391
|
-
mode:
|
|
394
|
+
mode: CleanCodeMode | undefined,
|
|
392
395
|
cwd: string | undefined,
|
|
393
396
|
serverUrl: string | undefined,
|
|
394
397
|
token: string | undefined,
|
|
@@ -422,10 +425,51 @@ async function getFilterCompletions(
|
|
|
422
425
|
return filtered.length > 0 ? filtered : null;
|
|
423
426
|
}
|
|
424
427
|
|
|
428
|
+
async function getAnalyzeCompletions(
|
|
429
|
+
current: string,
|
|
430
|
+
mode: CleanCodeMode | undefined,
|
|
431
|
+
cwd: string | undefined,
|
|
432
|
+
serverUrl: string | undefined,
|
|
433
|
+
token: string | undefined,
|
|
434
|
+
projectKey: string | undefined,
|
|
435
|
+
): Promise<AutocompleteItem[] | null> {
|
|
436
|
+
const [filterItems, aliasItems] = await Promise.all([
|
|
437
|
+
getFilterCompletions(current, mode, cwd, serverUrl, token, projectKey),
|
|
438
|
+
cwd ? getWorkspaceAliases(cwd, current) : Promise.resolve(null),
|
|
439
|
+
]);
|
|
440
|
+
if (!filterItems && !aliasItems) return null;
|
|
441
|
+
const combined: AutocompleteItem[] = [
|
|
442
|
+
...(aliasItems ?? []),
|
|
443
|
+
...(filterItems ?? []),
|
|
444
|
+
];
|
|
445
|
+
return combined.length > 0 ? combined : null;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function getWorkspaceAliases(
|
|
449
|
+
cwd: string,
|
|
450
|
+
current: string,
|
|
451
|
+
): Promise<AutocompleteItem[] | null> {
|
|
452
|
+
try {
|
|
453
|
+
const { registry } = await loadWorkspaceRegistry(cwd);
|
|
454
|
+
const aliases = knownTargets(registry);
|
|
455
|
+
if (aliases.length === 0) return null;
|
|
456
|
+
return filterAutocompleteItems(
|
|
457
|
+
aliases.map((alias) => ({
|
|
458
|
+
value: alias,
|
|
459
|
+
label: alias,
|
|
460
|
+
description: registry.workspaces[alias],
|
|
461
|
+
})),
|
|
462
|
+
current,
|
|
463
|
+
);
|
|
464
|
+
} catch {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
425
469
|
export async function sonarArgumentCompletions(
|
|
426
470
|
argumentText: string,
|
|
427
471
|
issues?: SonarIssue[],
|
|
428
|
-
mode?:
|
|
472
|
+
mode?: CleanCodeMode,
|
|
429
473
|
cwd?: string,
|
|
430
474
|
serverUrl?: string,
|
|
431
475
|
token?: string,
|
|
@@ -453,7 +497,18 @@ export async function sonarArgumentCompletions(
|
|
|
453
497
|
return getOpenCompletions(issues, mode, current);
|
|
454
498
|
}
|
|
455
499
|
|
|
456
|
-
if (lowerCommand === "analyze"
|
|
500
|
+
if (lowerCommand === "analyze") {
|
|
501
|
+
return getAnalyzeCompletions(
|
|
502
|
+
current,
|
|
503
|
+
mode,
|
|
504
|
+
cwd,
|
|
505
|
+
serverUrl,
|
|
506
|
+
token,
|
|
507
|
+
projectKey,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (lowerCommand === "issues") {
|
|
457
512
|
return getFilterCompletions(
|
|
458
513
|
current,
|
|
459
514
|
mode,
|
|
@@ -600,7 +655,7 @@ export function formatMetricsOutput(metrics: {
|
|
|
600
655
|
measures?: SonarDuplicationMeasures;
|
|
601
656
|
issueCounts?: IssueSeverityCounts;
|
|
602
657
|
issueQualityCounts?: IssueQualityCounts;
|
|
603
|
-
cleanCodeMode?:
|
|
658
|
+
cleanCodeMode?: CleanCodeMode;
|
|
604
659
|
}): string {
|
|
605
660
|
const lines: string[] = [`Metrics for ${metrics.projectKey}`];
|
|
606
661
|
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
4
|
import { basename, dirname, relative, resolve } from "node:path";
|
|
4
5
|
import type {
|
|
5
6
|
SonarInitConfig,
|
|
@@ -291,6 +292,155 @@ export function knownTargets(registry: SonarWorkspaceRegistry): string[] {
|
|
|
291
292
|
);
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
export interface DiscoveredTarget {
|
|
296
|
+
dir: string;
|
|
297
|
+
alias?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const SONARQUBE_CONFIG_FILE = ".pi/sonarqube.json";
|
|
301
|
+
|
|
302
|
+
// ── Target discovery ──────────────────────────────────────────────────────
|
|
303
|
+
|
|
304
|
+
async function execFd(repoRoot: string): Promise<string> {
|
|
305
|
+
return execFileSync(
|
|
306
|
+
"fd",
|
|
307
|
+
[
|
|
308
|
+
"--hidden",
|
|
309
|
+
"-g",
|
|
310
|
+
"sonarqube.json",
|
|
311
|
+
"--base-directory",
|
|
312
|
+
repoRoot,
|
|
313
|
+
"--max-depth",
|
|
314
|
+
"8",
|
|
315
|
+
"--exclude",
|
|
316
|
+
".git",
|
|
317
|
+
"--exclude",
|
|
318
|
+
"node_modules",
|
|
319
|
+
],
|
|
320
|
+
{
|
|
321
|
+
encoding: "utf8",
|
|
322
|
+
timeout: 5000,
|
|
323
|
+
maxBuffer: 1024 * 1024,
|
|
324
|
+
windowsHide: true,
|
|
325
|
+
},
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export async function discoverSonarQubeTargets(
|
|
330
|
+
repoRoot: string,
|
|
331
|
+
strategy?: "auto" | "fd" | "walk",
|
|
332
|
+
): Promise<DiscoveredTarget[]> {
|
|
333
|
+
if (strategy === "walk") {
|
|
334
|
+
return walkForSonarqubeConfigs(repoRoot);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Try fd first (auto default) or fd-only
|
|
338
|
+
try {
|
|
339
|
+
const output = await execFd(repoRoot);
|
|
340
|
+
const parsed = parseDiscoveredPaths(output, repoRoot);
|
|
341
|
+
if (parsed.length > 0 || strategy === "fd") return parsed;
|
|
342
|
+
} catch (_e) {
|
|
343
|
+
if (strategy === "fd") throw _e;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return walkForSonarqubeConfigs(repoRoot);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function parseDiscoveredPaths(
|
|
350
|
+
output: string,
|
|
351
|
+
repoRoot: string,
|
|
352
|
+
): DiscoveredTarget[] {
|
|
353
|
+
const lines = output.trim().split("\n").filter(Boolean);
|
|
354
|
+
const seen = new Set<string>();
|
|
355
|
+
const targets: DiscoveredTarget[] = [];
|
|
356
|
+
for (const line of lines) {
|
|
357
|
+
const dir = resolve(repoRoot, dirname(dirname(line)));
|
|
358
|
+
if (dir === repoRoot) continue;
|
|
359
|
+
const key = relative(repoRoot, dir);
|
|
360
|
+
if (seen.has(key)) continue;
|
|
361
|
+
seen.add(key);
|
|
362
|
+
targets.push({ dir, alias: undefined });
|
|
363
|
+
}
|
|
364
|
+
return targets;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function walkForSonarqubeConfigs(
|
|
368
|
+
repoRoot: string,
|
|
369
|
+
): Promise<DiscoveredTarget[]> {
|
|
370
|
+
const configDirs: string[] = [];
|
|
371
|
+
|
|
372
|
+
async function walk(dir: string, depth: number): Promise<void> {
|
|
373
|
+
if (depth > 6) return;
|
|
374
|
+
try {
|
|
375
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
376
|
+
for (const entry of entries) {
|
|
377
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
378
|
+
if (entry.name.startsWith(".") && entry.name !== ".pi") continue;
|
|
379
|
+
const fullPath = resolve(dir, entry.name);
|
|
380
|
+
if (entry.isDirectory()) {
|
|
381
|
+
await walk(fullPath, depth + 1);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Check if this dir has .pi/sonarqube.json
|
|
385
|
+
try {
|
|
386
|
+
await stat(resolve(dir, SONARQUBE_CONFIG_FILE));
|
|
387
|
+
if (dir !== repoRoot) configDirs.push(dir);
|
|
388
|
+
} catch {
|
|
389
|
+
// no config here
|
|
390
|
+
}
|
|
391
|
+
} catch {
|
|
392
|
+
// skip unreadable dirs
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
await walk(repoRoot, 0);
|
|
397
|
+
|
|
398
|
+
const seen = new Set<string>();
|
|
399
|
+
const targets: DiscoveredTarget[] = [];
|
|
400
|
+
for (const dir of configDirs) {
|
|
401
|
+
const key = relative(repoRoot, dir);
|
|
402
|
+
if (seen.has(key)) continue;
|
|
403
|
+
seen.add(key);
|
|
404
|
+
targets.push({ dir, alias: undefined });
|
|
405
|
+
}
|
|
406
|
+
return targets;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ── Registered + discovered target merger ───────────────────────────────────
|
|
410
|
+
|
|
411
|
+
export interface MappedTarget {
|
|
412
|
+
alias: string;
|
|
413
|
+
path: string;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Gather all available SonarQube targets from the workspace registry and from
|
|
418
|
+
* disk discovery (`.pi/sonarqube.json`). Registry entries take precedence.
|
|
419
|
+
*/
|
|
420
|
+
export async function gatherTargets(
|
|
421
|
+
startDir: string,
|
|
422
|
+
): Promise<{ repoRoot: string; targets: MappedTarget[] }> {
|
|
423
|
+
const { repoRoot, registry } = await loadWorkspaceRegistry(startDir);
|
|
424
|
+
const mapped = new Map<string, MappedTarget>();
|
|
425
|
+
|
|
426
|
+
for (const [alias, relPath] of Object.entries(registry.workspaces)) {
|
|
427
|
+
mapped.set(relPath, { alias, path: relPath });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const discovered = await discoverSonarQubeTargets(repoRoot);
|
|
431
|
+
for (const d of discovered) {
|
|
432
|
+
const relPath = relative(repoRoot, d.dir);
|
|
433
|
+
if (!mapped.has(relPath)) {
|
|
434
|
+
mapped.set(relPath, { alias: basename(d.dir), path: relPath });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
repoRoot,
|
|
440
|
+
targets: [...mapped.values()],
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
294
444
|
export async function resolveTarget(
|
|
295
445
|
ctx: { cwd: string },
|
|
296
446
|
targetInput?: string,
|
package/src/index.ts
CHANGED
|
@@ -70,6 +70,7 @@ import {
|
|
|
70
70
|
openIssuePreview,
|
|
71
71
|
resolveTargetState,
|
|
72
72
|
showDuplicationBrowser,
|
|
73
|
+
pickOrResolveTarget,
|
|
73
74
|
} from "./ui.js";
|
|
74
75
|
|
|
75
76
|
const SonarToolParams = Type.Object({
|
|
@@ -156,7 +157,11 @@ async function analyzeProject(
|
|
|
156
157
|
filters?: SonarIssueFetchOptions,
|
|
157
158
|
): Promise<SonarAnalysisState> {
|
|
158
159
|
const config = await resolveConfig(ctx, inputPath);
|
|
159
|
-
_cachedConfig = {
|
|
160
|
+
_cachedConfig = {
|
|
161
|
+
serverUrl: config.serverUrl,
|
|
162
|
+
projectKey: config.projectKey,
|
|
163
|
+
token: config.token,
|
|
164
|
+
};
|
|
160
165
|
const baseDirStat = await stat(config.baseDir).catch(() => undefined);
|
|
161
166
|
if (!baseDirStat?.isDirectory()) {
|
|
162
167
|
throw new Error(`Project directory not found: ${config.baseDir}`);
|
|
@@ -286,10 +291,16 @@ async function initCommandHandler(
|
|
|
286
291
|
ctx: ExtensionCommandContext,
|
|
287
292
|
options: InitCommandOptions = {},
|
|
288
293
|
): Promise<void> {
|
|
294
|
+
let { alias, targetInput } = options;
|
|
295
|
+
if (!alias && !targetInput) {
|
|
296
|
+
const pickResult = await pickOrResolveTarget(ctx);
|
|
297
|
+
if (pickResult === null) return;
|
|
298
|
+
if (pickResult !== undefined) targetInput = pickResult;
|
|
299
|
+
}
|
|
289
300
|
const { baseDir, repoRoot } = await resolveInitTarget(
|
|
290
301
|
ctx,
|
|
291
|
-
|
|
292
|
-
|
|
302
|
+
alias,
|
|
303
|
+
targetInput,
|
|
293
304
|
);
|
|
294
305
|
const existing = await loadProjectConfig(baseDir);
|
|
295
306
|
|
|
@@ -394,13 +405,18 @@ function renderIssueListResult(
|
|
|
394
405
|
// ── Extension entrypoint ────────────────────────────────────────────────────
|
|
395
406
|
|
|
396
407
|
// ponytail: lazy config cache so rule autocomplete can fetch from the server
|
|
397
|
-
let _cachedConfig:
|
|
408
|
+
let _cachedConfig:
|
|
409
|
+
{ serverUrl: string; projectKey: string; token?: string } | undefined;
|
|
398
410
|
|
|
399
411
|
async function cachedServerConfig(cwd: string): Promise<typeof _cachedConfig> {
|
|
400
412
|
if (!_cachedConfig) {
|
|
401
413
|
try {
|
|
402
414
|
const cfg = await resolveConfig({ cwd });
|
|
403
|
-
_cachedConfig = {
|
|
415
|
+
_cachedConfig = {
|
|
416
|
+
serverUrl: cfg.serverUrl,
|
|
417
|
+
projectKey: cfg.projectKey,
|
|
418
|
+
token: cfg.token,
|
|
419
|
+
};
|
|
404
420
|
} catch {
|
|
405
421
|
// fall through
|
|
406
422
|
}
|
|
@@ -719,6 +735,11 @@ async function commandAnalyze(
|
|
|
719
735
|
filters: SonarIssueFetchOptions | undefined,
|
|
720
736
|
rememberState: (s: SonarAnalysisState) => void,
|
|
721
737
|
): Promise<void> {
|
|
738
|
+
if (!targetInput) {
|
|
739
|
+
const pickResult = await pickOrResolveTarget(ctx);
|
|
740
|
+
if (pickResult === null) return; // user cancelled
|
|
741
|
+
targetInput = pickResult;
|
|
742
|
+
}
|
|
722
743
|
const state = await analyzeProject(pi, ctx, targetInput, filters);
|
|
723
744
|
rememberState(state);
|
|
724
745
|
if (ctx.hasUI) {
|
|
@@ -773,6 +794,11 @@ async function commandMetrics(
|
|
|
773
794
|
ctx: ExtensionCommandContext,
|
|
774
795
|
targetInput?: string,
|
|
775
796
|
): Promise<void> {
|
|
797
|
+
if (!targetInput) {
|
|
798
|
+
const pickResult = await pickOrResolveTarget(ctx);
|
|
799
|
+
if (pickResult === null) return;
|
|
800
|
+
targetInput = pickResult;
|
|
801
|
+
}
|
|
776
802
|
const config = await resolveConfig(ctx, targetInput);
|
|
777
803
|
const metrics = await loadMetricsData(config, ctx.signal);
|
|
778
804
|
if (!metrics) {
|
|
@@ -803,6 +829,11 @@ async function commandDuplications(
|
|
|
803
829
|
fileIndex?: number,
|
|
804
830
|
filters?: SonarIssueFetchOptions,
|
|
805
831
|
): Promise<void> {
|
|
832
|
+
if (!targetInput) {
|
|
833
|
+
const pickResult = await pickOrResolveTarget(ctx);
|
|
834
|
+
if (pickResult === null) return;
|
|
835
|
+
targetInput = pickResult;
|
|
836
|
+
}
|
|
806
837
|
const config = await resolveConfig(ctx, targetInput);
|
|
807
838
|
let files: FileDuplication[];
|
|
808
839
|
try {
|
|
@@ -914,10 +945,16 @@ async function commandIssuesOrOpen(
|
|
|
914
945
|
},
|
|
915
946
|
rememberState: (s: SonarAnalysisState) => void,
|
|
916
947
|
): Promise<void> {
|
|
948
|
+
let { targetInput } = parsed;
|
|
949
|
+
if (!targetInput) {
|
|
950
|
+
const pickResult = await pickOrResolveTarget(ctx);
|
|
951
|
+
if (pickResult === null) return;
|
|
952
|
+
targetInput = pickResult;
|
|
953
|
+
}
|
|
917
954
|
const targetState = await resolveTargetState(
|
|
918
955
|
ctx,
|
|
919
956
|
statesByBaseDir,
|
|
920
|
-
|
|
957
|
+
targetInput,
|
|
921
958
|
parsed.filters,
|
|
922
959
|
);
|
|
923
960
|
if (!targetState) {
|
package/src/types.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface SonarAnalysisState {
|
|
|
68
68
|
measures?: SonarDuplicationMeasures;
|
|
69
69
|
totalIssues: number;
|
|
70
70
|
issues: SonarIssue[];
|
|
71
|
-
cleanCodeMode?:
|
|
71
|
+
cleanCodeMode?: CleanCodeMode;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
export interface SonarProjectConfig {
|
|
@@ -98,6 +98,8 @@ export interface SonarInitConfig {
|
|
|
98
98
|
|
|
99
99
|
// ── Internal types shared by adapter and extension ──────────────────────────
|
|
100
100
|
|
|
101
|
+
export type CleanCodeMode = "STANDARD" | "MQR";
|
|
102
|
+
|
|
101
103
|
export interface SonarWorkspaceRegistry {
|
|
102
104
|
version: 1;
|
|
103
105
|
workspaces: Record<string, string>;
|