@ts-for-gir/reporter 4.0.0-beta.39 → 4.0.0-beta.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ts-for-gir/reporter",
3
- "version": "4.0.0-beta.39",
3
+ "version": "4.0.0-beta.41",
4
4
  "description": "Problem reporting and comprehensive generation analysis for ts-for-gir",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -37,7 +37,8 @@
37
37
  "analysis"
38
38
  ],
39
39
  "devDependencies": {
40
- "@types/node": "^24.11.0",
40
+ "@ts-for-gir/tsconfig": "^4.0.0-beta.41",
41
+ "@types/node": "^24.12.0",
41
42
  "typescript": "^5.9.3"
42
43
  },
43
44
  "dependencies": {
@@ -8,8 +8,9 @@ import { blue, gray, green, red, yellow, yellowBright } from "colorette";
8
8
  import { PACKAGE_VERSION } from "./constants.ts";
9
9
  import { analyzeError, analyzeWarning } from "./message-analyzer.ts";
10
10
  import { ReporterBase } from "./reporter-base.ts";
11
- import type { GenerationReport, ProblemEntry, ReporterConfig, ReportStatistics } from "./types/index.ts";
11
+ import type { GenerationReport, ReporterConfig } from "./types/index.ts";
12
12
  import { ProblemCategory, ProblemSeverity } from "./types/index.ts";
13
+ import { computeProblemStatistics, generateReportSummary, groupProblemsByCategory } from "./types/report.ts";
13
14
 
14
15
  /**
15
16
  * Console Reporter implementation with full logging capabilities
@@ -146,10 +147,16 @@ export class ConsoleReporter extends ReporterBase {
146
147
  // === Problem-specific reporting methods ===
147
148
 
148
149
  public reportTypeResolutionError(typeName: string, namespace: string, message: string, details?: string): void {
149
- this.addProblem(ProblemSeverity.ERROR, ProblemCategory.TYPE_RESOLUTION, message, details, typeName, namespace, {
150
- namespace,
150
+ this.addProblem(
151
+ ProblemSeverity.ERROR,
152
+ ProblemCategory.TYPE_RESOLUTION,
153
+ message,
154
+ details,
151
155
  typeName,
152
- });
156
+ namespace,
157
+ undefined,
158
+ namespace,
159
+ );
153
160
 
154
161
  if (this.config.verbose) {
155
162
  const txt = this.prependInfo(message, "ERROR:");
@@ -157,11 +164,23 @@ export class ConsoleReporter extends ReporterBase {
157
164
  }
158
165
  }
159
166
 
160
- public reportTypeResolutionWarning(typeName: string, namespace: string, message: string, details?: string): void {
161
- this.addProblem(ProblemSeverity.WARNING, ProblemCategory.TYPE_RESOLUTION, message, details, typeName, namespace, {
162
- namespace,
167
+ public reportTypeResolutionWarning(
168
+ typeName: string,
169
+ namespace: string,
170
+ message: string,
171
+ details?: string,
172
+ sourceModule?: string,
173
+ ): void {
174
+ this.addProblem(
175
+ ProblemSeverity.WARNING,
176
+ ProblemCategory.TYPE_RESOLUTION,
177
+ message,
178
+ details,
163
179
  typeName,
164
- });
180
+ namespace,
181
+ sourceModule ? { sourceModule } : undefined,
182
+ namespace,
183
+ );
165
184
 
166
185
  if (this.config.verbose) {
167
186
  const txt = this.prependInfo(message, "WARN:");
@@ -204,10 +223,16 @@ export class ConsoleReporter extends ReporterBase {
204
223
  public reportTypeConflict(conflictType: string, elementName: string, namespace: string, details?: string): void {
205
224
  const message = `Type conflict (${conflictType}): ${elementName}`;
206
225
 
207
- this.addProblem(ProblemSeverity.WARNING, ProblemCategory.TYPE_CONFLICT, message, details, elementName, namespace, {
208
- conflictType,
226
+ this.addProblem(
227
+ ProblemSeverity.WARNING,
228
+ ProblemCategory.TYPE_CONFLICT,
229
+ message,
230
+ details,
231
+ elementName,
209
232
  namespace,
210
- });
233
+ { conflictType },
234
+ namespace,
235
+ );
211
236
 
212
237
  if (this.config.verbose) {
213
238
  const txt = this.prependInfo(message, "WARN:");
@@ -235,182 +260,9 @@ export class ConsoleReporter extends ReporterBase {
235
260
 
236
261
  // === Report generation methods ===
237
262
 
238
- private generateStatistics(): ReportStatistics {
239
- const bySeverity = Object.values(ProblemSeverity).reduce(
240
- (acc, severity) => {
241
- acc[severity] = 0;
242
- return acc;
243
- },
244
- {} as Record<ProblemSeverity, number>,
245
- );
246
-
247
- const byCategory = Object.values(ProblemCategory).reduce(
248
- (acc, category) => {
249
- acc[category] = 0;
250
- return acc;
251
- },
252
- {} as Record<ProblemCategory, number>,
253
- );
254
-
255
- const byModule: Record<string, number> = {};
256
-
257
- // Type-specific tracking
258
- const unresolvedTypes: Record<string, { count: number; namespaces: Set<string> }> = {};
259
- const typeConflicts: Record<string, { count: number; examples: Set<string> }> = {};
260
- const namespaceProblems: Record<string, { count: number; types: Set<string> }> = {};
261
-
262
- for (const problem of this.problems) {
263
- bySeverity[problem.severity]++;
264
- byCategory[problem.category]++;
265
- byModule[problem.module] = (byModule[problem.module] || 0) + 1;
266
-
267
- // Track type resolution problems
268
- if (problem.category === ProblemCategory.TYPE_RESOLUTION && problem.typeName) {
269
- if (!unresolvedTypes[problem.typeName]) {
270
- unresolvedTypes[problem.typeName] = { count: 0, namespaces: new Set() };
271
- }
272
- unresolvedTypes[problem.typeName].count++;
273
- if (problem.location) {
274
- unresolvedTypes[problem.typeName].namespaces.add(problem.location);
275
- }
276
-
277
- // Track namespace problems
278
- if (problem.location) {
279
- if (!namespaceProblems[problem.location]) {
280
- namespaceProblems[problem.location] = { count: 0, types: new Set() };
281
- }
282
- namespaceProblems[problem.location].count++;
283
- namespaceProblems[problem.location].types.add(problem.typeName);
284
- }
285
- }
286
-
287
- // Track type conflicts
288
- if (problem.category === ProblemCategory.TYPE_CONFLICT && problem.metadata?.conflictType) {
289
- const conflictType = problem.metadata.conflictType as string;
290
- if (!typeConflicts[conflictType]) {
291
- typeConflicts[conflictType] = { count: 0, examples: new Set() };
292
- }
293
- typeConflicts[conflictType].count++;
294
- if (problem.typeName) {
295
- typeConflicts[conflictType].examples.add(problem.typeName);
296
- }
297
- }
298
- }
299
-
300
- // Convert to arrays and sort
301
- const commonUnresolvedTypes = Object.entries(unresolvedTypes)
302
- .map(([type, data]) => ({
303
- type,
304
- count: data.count,
305
- namespaces: Array.from(data.namespaces),
306
- }))
307
- .sort((a, b) => b.count - a.count)
308
- .slice(0, 20);
309
-
310
- const commonTypeConflicts = Object.entries(typeConflicts)
311
- .map(([conflictType, data]) => ({
312
- conflictType,
313
- count: data.count,
314
- examples: Array.from(data.examples).slice(0, 5),
315
- }))
316
- .sort((a, b) => b.count - a.count);
317
-
318
- const problematicNamespaces = Object.entries(namespaceProblems)
319
- .map(([namespace, data]) => ({
320
- namespace,
321
- problems: data.count,
322
- types: Array.from(data.types).slice(0, 10),
323
- }))
324
- .sort((a, b) => b.problems - a.problems)
325
- .slice(0, 10);
326
-
327
- const mostProblematicModules = Object.entries(byModule)
328
- .sort(([, a], [, b]) => b - a)
329
- .slice(0, 10)
330
- .map(([module, count]) => ({ module, count }));
331
-
332
- const endTime = new Date();
333
- const durationMs = endTime.getTime() - this.startTime.getTime();
334
-
335
- return {
336
- bySeverity,
337
- byCategory,
338
- byModule,
339
- totalProblems: this.problems.length,
340
- mostProblematicModules,
341
- typeStatistics: {
342
- commonUnresolvedTypes,
343
- commonTypeConflicts,
344
- problematicNamespaces,
345
- },
346
- startTime: this.startTime,
347
- endTime,
348
- durationMs,
349
- };
350
- }
351
-
352
- private generateSummary(statistics: ReportStatistics): GenerationReport["summary"] {
353
- const { bySeverity, byCategory, totalProblems } = statistics;
354
-
355
- let status: "success" | "partial" = "success";
356
- if (bySeverity[ProblemSeverity.ERROR] > 0 || bySeverity[ProblemSeverity.WARNING] > 20) {
357
- status = "partial";
358
- }
359
-
360
- const keyIssues: string[] = [];
361
- const recommendations: string[] = [];
362
-
363
- // Analyze key issues
364
- if (byCategory[ProblemCategory.TYPE_RESOLUTION] > 0) {
365
- keyIssues.push(`${byCategory[ProblemCategory.TYPE_RESOLUTION]} type resolution issues detected`);
366
- recommendations.push("Review GIR files for missing or incorrect type definitions");
367
- }
368
-
369
- if (byCategory[ProblemCategory.PARSING_FAILURE] > 0) {
370
- keyIssues.push(`${byCategory[ProblemCategory.PARSING_FAILURE]} parsing failures occurred`);
371
- recommendations.push("Check GIR file syntax and ensure proper introspection data");
372
- }
373
-
374
- if (byCategory[ProblemCategory.GENERATION_FAILURE] > 0) {
375
- keyIssues.push(`${byCategory[ProblemCategory.GENERATION_FAILURE]} generation failures encountered`);
376
- recommendations.push("Review template configuration and output settings");
377
- }
378
-
379
- if (byCategory[ProblemCategory.TYPE_CONFLICT] > 5) {
380
- keyIssues.push(`High number of type conflicts (${byCategory[ProblemCategory.TYPE_CONFLICT]})`);
381
- recommendations.push("Consider using ignore patterns or updating GIR files to resolve conflicts");
382
- }
383
-
384
- if (keyIssues.length === 0 && totalProblems > 0) {
385
- keyIssues.push(`${totalProblems} minor issues detected`);
386
- }
387
-
388
- if (recommendations.length === 0 && totalProblems > 0) {
389
- recommendations.push("Review detailed problem list for specific improvement opportunities");
390
- }
391
-
392
- return {
393
- status,
394
- keyIssues,
395
- recommendations,
396
- };
397
- }
398
-
399
263
  public generateReport(): GenerationReport {
400
- const statistics = this.generateStatistics();
401
- const summary = this.generateSummary(statistics);
402
-
403
- const problemsByCategory = Object.values(ProblemCategory).reduce(
404
- (acc, category) => {
405
- acc[category] = [];
406
- return acc;
407
- },
408
- {} as Record<ProblemCategory, ProblemEntry[]>,
409
- );
410
-
411
- for (const problem of this.problems) {
412
- problemsByCategory[problem.category].push(problem);
413
- }
264
+ const statistics = computeProblemStatistics(this.problems, this.startTime);
265
+ const summary = generateReportSummary(statistics);
414
266
 
415
267
  return {
416
268
  metadata: {
@@ -419,7 +271,7 @@ export class ConsoleReporter extends ReporterBase {
419
271
  },
420
272
  statistics,
421
273
  problems: [...this.problems],
422
- problemsByCategory,
274
+ problemsByCategory: groupProblemsByCategory(this.problems),
423
275
  summary,
424
276
  };
425
277
  }
@@ -455,8 +307,11 @@ export class ConsoleReporter extends ReporterBase {
455
307
  console.log("=".repeat(50));
456
308
 
457
309
  // Status
458
- const statusColor = summary.status === "success" ? green : summary.status === "partial" ? yellow : red;
310
+ const statusColor = summary.status === "partial" ? yellow : green;
459
311
  console.log(`Status: ${statusColor(summary.status.toUpperCase())}`);
312
+ if (summary.status === "success_with_warnings") {
313
+ console.log(green(" All issues are non-blocking. Generated types are complete."));
314
+ }
460
315
 
461
316
  // Statistics
462
317
  console.log(`\n📈 Statistics:`);
@@ -481,13 +336,17 @@ export class ConsoleReporter extends ReporterBase {
481
336
 
482
337
  // Type-specific statistics
483
338
  if (statistics.typeStatistics.commonUnresolvedTypes.length > 0) {
484
- console.log(`\nMost Common Unresolved Types:`);
485
- statistics.typeStatistics.commonUnresolvedTypes.slice(0, 10).forEach(({ type, count, namespaces }) => {
486
- console.log(` ${red(type)}: ${count} occurrences in ${namespaces.length} namespace(s)`);
487
- if (namespaces.length <= 3) {
488
- console.log(` └─ ${gray(namespaces.join(", "))}`);
489
- }
490
- });
339
+ console.log(`\n⚠️ Most Common Unresolved Types (produce 'never' type):`);
340
+ statistics.typeStatistics.commonUnresolvedTypes
341
+ .slice(0, 10)
342
+ .forEach(({ type, count, namespaces, sourceModules }) => {
343
+ console.log(` ${yellow(type)}: ${count} occurrences (origin: ${namespaces.join(", ") || "unknown"})`);
344
+ if (sourceModules.length > 0) {
345
+ const moduleList = sourceModules.slice(0, 5).join(", ");
346
+ const moreModules = sourceModules.length > 5 ? ` and ${sourceModules.length - 5} more` : "";
347
+ console.log(` └─ Referenced from: ${gray(moduleList + moreModules)}`);
348
+ }
349
+ });
491
350
  }
492
351
 
493
352
  if (statistics.typeStatistics.commonTypeConflicts.length > 0) {
@@ -501,9 +360,9 @@ export class ConsoleReporter extends ReporterBase {
501
360
  }
502
361
 
503
362
  if (statistics.typeStatistics.problematicNamespaces.length > 0) {
504
- console.log(`\n🚨 Most Problematic Namespaces:`);
363
+ console.log(`\n📂 Namespaces with Most Warnings:`);
505
364
  statistics.typeStatistics.problematicNamespaces.slice(0, 5).forEach(({ namespace, problems, types }) => {
506
- console.log(` ${namespace}: ${problems} problems`);
365
+ console.log(` ${namespace}: ${problems} warnings`);
507
366
  if (types.length > 0) {
508
367
  const typeList = types.slice(0, 5).join(", ");
509
368
  const moreTypes = types.length > 5 ? ` and ${types.length - 5} more` : "";
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Backwards compatibility export - alias ConsoleReporter as Reporter
2
2
  export { ConsoleReporter, ConsoleReporter as Reporter } from "./console-reporter.ts";
3
+ export { LazyReporter } from "./lazy-reporter.ts";
3
4
  export type { AnalyzedMessage } from "./message-analyzer.ts";
4
5
  export { analyzeError, analyzeWarning } from "./message-analyzer.ts";
5
6
  export { ReporterBase } from "./reporter-base.ts";
@@ -0,0 +1,35 @@
1
+ import { ConsoleReporter } from "./console-reporter.ts";
2
+ import { ReporterService } from "./reporter-service.ts";
3
+
4
+ /**
5
+ * A lazily-initialized reporter that auto-registers with ReporterService.
6
+ * Consolidates the repeated lazy-init + configure pattern used across the codebase.
7
+ */
8
+ export class LazyReporter {
9
+ private config = { enabled: false, output: "ts-for-gir-report.json" };
10
+ private instance: ConsoleReporter | null = null;
11
+
12
+ constructor(private readonly name: string) {}
13
+
14
+ configure(enabled: boolean, output = "ts-for-gir-report.json"): void {
15
+ this.config = { enabled, output };
16
+ this.instance = null;
17
+
18
+ if (enabled) {
19
+ this.instance = new ConsoleReporter(true, this.name, enabled, output);
20
+ ReporterService.getInstance().registerReporter(this.name, this.instance);
21
+ }
22
+ }
23
+
24
+ get(): ConsoleReporter {
25
+ if (!this.instance) {
26
+ const { enabled, output } = this.config;
27
+ this.instance = new ConsoleReporter(true, this.name, enabled, output);
28
+
29
+ if (enabled) {
30
+ ReporterService.getInstance().registerReporter(this.name, this.instance);
31
+ }
32
+ }
33
+ return this.instance;
34
+ }
35
+ }
@@ -71,13 +71,13 @@ export function analyzeWarning(message: string, args?: unknown[]): AnalyzedMessa
71
71
  export function analyzeError(message: string, args?: unknown[]): AnalyzedMessage | null {
72
72
  const details = args && args.length > 0 ? JSON.stringify(args) : undefined;
73
73
 
74
- // Type resolution errors
74
+ // Type resolution failures are non-fatal (produce 'never' type), classify as WARNING
75
75
  if (message.includes("Unable to resolve type") || message.includes("could not be resolved")) {
76
76
  const unresolvedMatch = message.match(/Unable to resolve type (\w+) in same namespace (\w+)!/);
77
77
  if (unresolvedMatch) {
78
78
  const [, typeName, namespace] = unresolvedMatch;
79
79
  return {
80
- severity: ProblemSeverity.ERROR,
80
+ severity: ProblemSeverity.WARNING,
81
81
  category: ProblemCategory.TYPE_RESOLUTION,
82
82
  typeName,
83
83
  namespace,
@@ -93,12 +93,12 @@ export function analyzeError(message: string, args?: unknown[]): AnalyzedMessage
93
93
  const namespace = namespaceMatch ? namespaceMatch[1] : context;
94
94
 
95
95
  return {
96
- severity: ProblemSeverity.ERROR,
96
+ severity: ProblemSeverity.WARNING,
97
97
  category: ProblemCategory.TYPE_RESOLUTION,
98
98
  typeName,
99
99
  namespace,
100
100
  details,
101
- metadata: { namespace, typeName, context },
101
+ metadata: { namespace, typeName, context, resolutionType: "cross_namespace" },
102
102
  };
103
103
  }
104
104
  }
@@ -42,6 +42,8 @@ export abstract class ReporterBase {
42
42
  typeName?: string,
43
43
  location?: string,
44
44
  metadata?: Record<string, unknown>,
45
+ /** Override the module field with the actual GIR namespace instead of the reporter name */
46
+ moduleOverride?: string,
45
47
  ): void {
46
48
  if (!this.config.enabled) {
47
49
  return;
@@ -51,7 +53,7 @@ export abstract class ReporterBase {
51
53
  id: this.generateProblemId(),
52
54
  severity,
53
55
  category,
54
- module: this.config.moduleName,
56
+ module: moduleOverride || this.config.moduleName,
55
57
  message,
56
58
  details,
57
59
  typeName,
@@ -105,12 +107,14 @@ export abstract class ReporterBase {
105
107
 
106
108
  /**
107
109
  * Report a type resolution warning (fallback cases)
110
+ * @param sourceModule - The GIR module that references this type (e.g., "Shell 17")
108
111
  */
109
112
  public abstract reportTypeResolutionWarning(
110
113
  typeName: string,
111
114
  namespace: string,
112
115
  message: string,
113
116
  details?: string,
117
+ sourceModule?: string,
114
118
  ): void;
115
119
 
116
120
  /**
@@ -8,8 +8,8 @@ import { resolve } from "node:path";
8
8
  import { blue, green, red, yellow } from "colorette";
9
9
  import { PACKAGE_VERSION } from "./constants.ts";
10
10
  import type { ReporterBase } from "./reporter-base.ts";
11
- import type { GenerationReport, ProblemEntry, ReportStatistics } from "./types/index.ts";
12
- import { ProblemCategory, ProblemSeverity } from "./types/index.ts";
11
+ import type { GenerationReport, ProblemEntry } from "./types/index.ts";
12
+ import { computeProblemStatistics, generateReportSummary, groupProblemsByCategory } from "./types/report.ts";
13
13
 
14
14
  /**
15
15
  * Centralized service for managing multiple Reporter instances
@@ -83,8 +83,7 @@ export class ReporterService {
83
83
  const allProblems: ProblemEntry[] = [];
84
84
 
85
85
  for (const reporter of this.reporters.values()) {
86
- const report = reporter.generateReport();
87
- allProblems.push(...report.problems);
86
+ allProblems.push(...reporter.getProblems());
88
87
  }
89
88
 
90
89
  return allProblems;
@@ -93,231 +92,14 @@ export class ReporterService {
93
92
  /**
94
93
  * Generate comprehensive statistics from all reporters
95
94
  */
96
- private generateComprehensiveStatistics(): ReportStatistics {
97
- const allProblems = this.collectAllProblems();
98
-
99
- // If we have no reporters, return empty stats
100
- if (this.reporters.size === 0) {
101
- return {
102
- bySeverity: {
103
- [ProblemSeverity.DEBUG]: 0,
104
- [ProblemSeverity.INFO]: 0,
105
- [ProblemSeverity.WARNING]: 0,
106
- [ProblemSeverity.ERROR]: 0,
107
- [ProblemSeverity.CRITICAL]: 0,
108
- },
109
- byCategory: {
110
- [ProblemCategory.TYPE_RESOLUTION]: 0,
111
- [ProblemCategory.PARSING_FAILURE]: 0,
112
- [ProblemCategory.GENERATION_FAILURE]: 0,
113
- [ProblemCategory.TYPE_CONFLICT]: 0,
114
- [ProblemCategory.DEPENDENCY_ISSUE]: 0,
115
- [ProblemCategory.CONFIGURATION]: 0,
116
- [ProblemCategory.IO_ERROR]: 0,
117
- [ProblemCategory.GENERAL]: 0,
118
- },
119
- byModule: {},
120
- totalProblems: 0,
121
- mostProblematicModules: [],
122
- typeStatistics: {
123
- commonUnresolvedTypes: [],
124
- commonTypeConflicts: [],
125
- problematicNamespaces: [],
126
- },
127
- startTime: new Date(),
128
- endTime: new Date(),
129
- durationMs: 0,
130
- };
131
- }
132
-
133
- // Get the first reporter's report as base for timing
134
- const firstReport = this.reporters.values().next().value?.generateReport();
135
- const startTime = firstReport?.statistics.startTime || new Date();
136
-
137
- // Use current time as end time
138
- const endTime = new Date();
139
- const durationMs = endTime.getTime() - startTime.getTime();
140
-
141
- // Aggregate statistics
142
- const bySeverity: Record<ProblemSeverity, number> = {
143
- [ProblemSeverity.DEBUG]: 0,
144
- [ProblemSeverity.INFO]: 0,
145
- [ProblemSeverity.WARNING]: 0,
146
- [ProblemSeverity.ERROR]: 0,
147
- [ProblemSeverity.CRITICAL]: 0,
148
- };
149
-
150
- const byCategory: Record<ProblemCategory, number> = {
151
- [ProblemCategory.TYPE_RESOLUTION]: 0,
152
- [ProblemCategory.PARSING_FAILURE]: 0,
153
- [ProblemCategory.GENERATION_FAILURE]: 0,
154
- [ProblemCategory.TYPE_CONFLICT]: 0,
155
- [ProblemCategory.DEPENDENCY_ISSUE]: 0,
156
- [ProblemCategory.CONFIGURATION]: 0,
157
- [ProblemCategory.IO_ERROR]: 0,
158
- [ProblemCategory.GENERAL]: 0,
159
- };
160
-
161
- const byModule: Record<string, number> = {};
162
-
163
- // Type-specific tracking
164
- const unresolvedTypes: Record<string, { count: number; namespaces: Set<string> }> = {};
165
- const typeConflicts: Record<string, { count: number; examples: Set<string> }> = {};
166
- const namespaceProblems: Record<string, { count: number; types: Set<string> }> = {};
167
-
168
- for (const problem of allProblems) {
169
- bySeverity[problem.severity] = (bySeverity[problem.severity] || 0) + 1;
170
- byCategory[problem.category] = (byCategory[problem.category] || 0) + 1;
171
- byModule[problem.module] = (byModule[problem.module] || 0) + 1;
172
-
173
- // Track type resolution problems
174
- if (problem.category === ProblemCategory.TYPE_RESOLUTION && problem.typeName) {
175
- if (!unresolvedTypes[problem.typeName]) {
176
- unresolvedTypes[problem.typeName] = { count: 0, namespaces: new Set() };
177
- }
178
- unresolvedTypes[problem.typeName].count++;
179
- if (problem.location) {
180
- unresolvedTypes[problem.typeName].namespaces.add(problem.location);
181
- }
182
-
183
- // Track namespace problems
184
- if (problem.location) {
185
- if (!namespaceProblems[problem.location]) {
186
- namespaceProblems[problem.location] = { count: 0, types: new Set() };
187
- }
188
- namespaceProblems[problem.location].count++;
189
- namespaceProblems[problem.location].types.add(problem.typeName);
190
- }
191
- }
192
-
193
- // Track type conflicts
194
- if (problem.category === ProblemCategory.TYPE_CONFLICT && problem.metadata?.conflictType) {
195
- const conflictType = problem.metadata.conflictType as string;
196
- if (!typeConflicts[conflictType]) {
197
- typeConflicts[conflictType] = { count: 0, examples: new Set() };
198
- }
199
- typeConflicts[conflictType].count++;
200
- if (problem.typeName) {
201
- typeConflicts[conflictType].examples.add(problem.typeName);
202
- }
203
- }
204
- }
205
-
206
- // Convert to arrays and sort
207
- const commonUnresolvedTypes = Object.entries(unresolvedTypes)
208
- .map(([type, data]) => ({
209
- type,
210
- count: data.count,
211
- namespaces: Array.from(data.namespaces),
212
- }))
213
- .sort((a, b) => b.count - a.count)
214
- .slice(0, 20);
215
-
216
- const commonTypeConflicts = Object.entries(typeConflicts)
217
- .map(([conflictType, data]) => ({
218
- conflictType,
219
- count: data.count,
220
- examples: Array.from(data.examples).slice(0, 5),
221
- }))
222
- .sort((a, b) => b.count - a.count);
223
-
224
- const problematicNamespaces = Object.entries(namespaceProblems)
225
- .map(([namespace, data]) => ({
226
- namespace,
227
- problems: data.count,
228
- types: Array.from(data.types).slice(0, 10),
229
- }))
230
- .sort((a, b) => b.problems - a.problems)
231
- .slice(0, 10);
232
-
233
- const mostProblematicModules = Object.entries(byModule)
234
- .sort(([, a], [, b]) => b - a)
235
- .slice(0, 10)
236
- .map(([module, count]) => ({ module, count }));
237
-
238
- return {
239
- bySeverity,
240
- byCategory,
241
- byModule,
242
- totalProblems: allProblems.length,
243
- mostProblematicModules,
244
- typeStatistics: {
245
- commonUnresolvedTypes,
246
- commonTypeConflicts,
247
- problematicNamespaces,
248
- },
249
- startTime,
250
- endTime,
251
- durationMs,
252
- };
253
- }
254
-
255
95
  /**
256
96
  * Generate comprehensive report from all reporters
257
97
  */
258
98
  public generateComprehensiveReport(): GenerationReport {
259
- const statistics = this.generateComprehensiveStatistics();
260
99
  const allProblems = this.collectAllProblems();
261
-
262
- // Generate problems by category
263
- const problemsByCategory = Object.values(ProblemCategory).reduce(
264
- (acc, category) => {
265
- acc[category] = [];
266
- return acc;
267
- },
268
- {} as Record<ProblemCategory, ProblemEntry[]>,
269
- );
270
-
271
- for (const problem of allProblems) {
272
- problemsByCategory[problem.category].push(problem);
273
- }
274
-
275
- // Generate summary
276
- const errorCount = statistics.bySeverity[ProblemSeverity.ERROR] || 0;
277
- const _criticalCount = statistics.bySeverity[ProblemSeverity.CRITICAL] || 0;
278
- const warningCount = statistics.bySeverity[ProblemSeverity.WARNING] || 0;
279
-
280
- let status: "success" | "partial" = "success";
281
- if (errorCount > 0 || warningCount > 20) {
282
- status = "partial";
283
- }
284
-
285
- const keyIssues: string[] = [];
286
- const recommendations: string[] = [];
287
-
288
- // Analyze key issues across all modules
289
- const typeResolutionCount = statistics.byCategory[ProblemCategory.TYPE_RESOLUTION] || 0;
290
- const parsingFailureCount = statistics.byCategory[ProblemCategory.PARSING_FAILURE] || 0;
291
- const generationFailureCount = statistics.byCategory[ProblemCategory.GENERATION_FAILURE] || 0;
292
- const conflictCount = statistics.byCategory[ProblemCategory.TYPE_CONFLICT] || 0;
293
-
294
- if (typeResolutionCount > 0) {
295
- keyIssues.push(`${typeResolutionCount} type resolution issues across all modules`);
296
- recommendations.push("Review GIR files for missing or incorrect type definitions");
297
- }
298
-
299
- if (parsingFailureCount > 0) {
300
- keyIssues.push(`${parsingFailureCount} parsing failures encountered`);
301
- recommendations.push("Check GIR file syntax and ensure proper introspection data");
302
- }
303
-
304
- if (generationFailureCount > 0) {
305
- keyIssues.push(`${generationFailureCount} generation failures occurred`);
306
- recommendations.push("Review template configuration and output settings");
307
- }
308
-
309
- if (conflictCount > 10) {
310
- keyIssues.push(`High number of type conflicts (${conflictCount})`);
311
- recommendations.push("Consider using ignore patterns or updating GIR files to resolve conflicts");
312
- }
313
-
314
- if (keyIssues.length === 0 && statistics.totalProblems > 0) {
315
- keyIssues.push(`${statistics.totalProblems} minor issues detected across all modules`);
316
- }
317
-
318
- if (recommendations.length === 0 && statistics.totalProblems > 0) {
319
- recommendations.push("Review detailed problem list for specific improvement opportunities");
320
- }
100
+ const startTime = this.reporters.values().next().value?.getProblems()[0]?.timestamp || new Date();
101
+ const statistics = computeProblemStatistics(allProblems, startTime);
102
+ const summary = generateReportSummary(statistics);
321
103
 
322
104
  return {
323
105
  metadata: {
@@ -326,24 +108,20 @@ export class ReporterService {
326
108
  },
327
109
  statistics,
328
110
  problems: allProblems,
329
- problemsByCategory,
330
- summary: {
331
- status,
332
- keyIssues,
333
- recommendations,
334
- },
111
+ problemsByCategory: groupProblemsByCategory(allProblems),
112
+ summary,
335
113
  };
336
114
  }
337
115
 
338
116
  /**
339
117
  * Save comprehensive report to file
340
118
  */
341
- public async saveComprehensiveReport(outputPath?: string): Promise<void> {
119
+ public async saveComprehensiveReport(outputPath?: string, precomputedReport?: GenerationReport): Promise<void> {
342
120
  if (!this.config.enabled) {
343
121
  return;
344
122
  }
345
123
 
346
- const report = this.generateComprehensiveReport();
124
+ const report = precomputedReport || this.generateComprehensiveReport();
347
125
  const filePath = outputPath || this.config.outputPath;
348
126
 
349
127
  try {
@@ -358,8 +136,8 @@ export class ReporterService {
358
136
  /**
359
137
  * Print comprehensive summary to console
360
138
  */
361
- public printComprehensiveSummary(): void {
362
- const report = this.generateComprehensiveReport();
139
+ public printComprehensiveSummary(precomputedReport?: GenerationReport): void {
140
+ const report = precomputedReport || this.generateComprehensiveReport();
363
141
  const { statistics, summary } = report;
364
142
 
365
143
  console.log(`\n${"=".repeat(60)}`);
@@ -367,8 +145,11 @@ export class ReporterService {
367
145
  console.log("=".repeat(60));
368
146
 
369
147
  // Overall status
370
- const statusColor = summary.status === "success" ? green : yellow;
148
+ const statusColor = summary.status === "partial" ? yellow : green;
371
149
  console.log(`\n🎯 Overall Status: ${statusColor(summary.status.toUpperCase())}`);
150
+ if (summary.status === "success_with_warnings") {
151
+ console.log(green(" All issues are non-blocking. Generated types are complete."));
152
+ }
372
153
 
373
154
  // Total statistics
374
155
  console.log(`\n📈 Total Statistics:`);
@@ -396,9 +177,9 @@ export class ReporterService {
396
177
  }
397
178
  }
398
179
 
399
- // Most problematic modules
180
+ // Most problematic namespaces (byModule now tracks GIR namespaces)
400
181
  if (statistics.mostProblematicModules.length > 0) {
401
- console.log(`\n📦 Most Problematic Modules:`);
182
+ console.log(`\n📦 Namespaces with Most Issues:`);
402
183
  statistics.mostProblematicModules.slice(0, 10).forEach(({ module, count }) => {
403
184
  const percentage = Math.round((count / statistics.totalProblems) * 100);
404
185
  console.log(` ${module}: ${count} issues (${percentage}%)`);
@@ -1,11 +1,226 @@
1
- import type { ProblemCategory, ProblemEntry, ProblemSeverity } from "./problem.ts";
1
+ import { ProblemCategory, type ProblemEntry, ProblemSeverity } from "./problem.ts";
2
+
3
+ /**
4
+ * Generation status levels
5
+ */
6
+ export type GenerationStatus = "success" | "success_with_warnings" | "partial";
7
+
8
+ /**
9
+ * Determine the generation status based on problem statistics.
10
+ * - "partial": actual errors, parsing failures, or generation failures occurred
11
+ * - "success_with_warnings": generation completed but with non-blocking warnings
12
+ * - "success": no problems at all
13
+ */
14
+ export function determineGenerationStatus(
15
+ bySeverity: Record<ProblemSeverity, number>,
16
+ byCategory: Record<ProblemCategory, number>,
17
+ ): GenerationStatus {
18
+ const hasErrors = (bySeverity[ProblemSeverity.ERROR] || 0) > 0;
19
+ const hasCritical = (bySeverity[ProblemSeverity.CRITICAL] || 0) > 0;
20
+ const hasGenerationFailures = (byCategory[ProblemCategory.GENERATION_FAILURE] || 0) > 0;
21
+ const hasParsingFailures = (byCategory[ProblemCategory.PARSING_FAILURE] || 0) > 0;
22
+
23
+ if (hasErrors || hasCritical || hasGenerationFailures || hasParsingFailures) {
24
+ return "partial";
25
+ }
26
+
27
+ if ((bySeverity[ProblemSeverity.WARNING] || 0) > 0) {
28
+ return "success_with_warnings";
29
+ }
30
+
31
+ return "success";
32
+ }
33
+
34
+ /**
35
+ * Compute problem statistics from a list of problems.
36
+ * Shared between ConsoleReporter and ReporterService to avoid duplication.
37
+ */
38
+ export function computeProblemStatistics(problems: ProblemEntry[], startTime: Date): ReportStatistics {
39
+ const bySeverity: Record<ProblemSeverity, number> = {
40
+ [ProblemSeverity.DEBUG]: 0,
41
+ [ProblemSeverity.INFO]: 0,
42
+ [ProblemSeverity.WARNING]: 0,
43
+ [ProblemSeverity.ERROR]: 0,
44
+ [ProblemSeverity.CRITICAL]: 0,
45
+ };
46
+
47
+ const byCategory: Record<ProblemCategory, number> = {
48
+ [ProblemCategory.TYPE_RESOLUTION]: 0,
49
+ [ProblemCategory.PARSING_FAILURE]: 0,
50
+ [ProblemCategory.GENERATION_FAILURE]: 0,
51
+ [ProblemCategory.TYPE_CONFLICT]: 0,
52
+ [ProblemCategory.DEPENDENCY_ISSUE]: 0,
53
+ [ProblemCategory.CONFIGURATION]: 0,
54
+ [ProblemCategory.IO_ERROR]: 0,
55
+ [ProblemCategory.GENERAL]: 0,
56
+ };
57
+
58
+ const byModule: Record<string, number> = {};
59
+
60
+ const unresolvedTypes: Record<string, { count: number; namespaces: Set<string>; sourceModules: Set<string> }> = {};
61
+ const typeConflicts: Record<string, { count: number; examples: Set<string> }> = {};
62
+ const namespaceProblems: Record<string, { count: number; types: Set<string> }> = {};
63
+
64
+ for (const problem of problems) {
65
+ bySeverity[problem.severity]++;
66
+ byCategory[problem.category]++;
67
+ byModule[problem.module] = (byModule[problem.module] || 0) + 1;
68
+
69
+ if (problem.category === ProblemCategory.TYPE_RESOLUTION && problem.typeName) {
70
+ if (!unresolvedTypes[problem.typeName]) {
71
+ unresolvedTypes[problem.typeName] = { count: 0, namespaces: new Set(), sourceModules: new Set() };
72
+ }
73
+ unresolvedTypes[problem.typeName].count++;
74
+ if (problem.location) {
75
+ unresolvedTypes[problem.typeName].namespaces.add(problem.location);
76
+ }
77
+ const sourceModule = problem.metadata?.sourceModule as string | undefined;
78
+ if (sourceModule) {
79
+ unresolvedTypes[problem.typeName].sourceModules.add(sourceModule);
80
+ }
81
+
82
+ if (problem.location) {
83
+ if (!namespaceProblems[problem.location]) {
84
+ namespaceProblems[problem.location] = { count: 0, types: new Set() };
85
+ }
86
+ namespaceProblems[problem.location].count++;
87
+ namespaceProblems[problem.location].types.add(problem.typeName);
88
+ }
89
+ }
90
+
91
+ if (problem.category === ProblemCategory.TYPE_CONFLICT && problem.metadata?.conflictType) {
92
+ const conflictType = problem.metadata.conflictType as string;
93
+ if (!typeConflicts[conflictType]) {
94
+ typeConflicts[conflictType] = { count: 0, examples: new Set() };
95
+ }
96
+ typeConflicts[conflictType].count++;
97
+ if (problem.typeName) {
98
+ typeConflicts[conflictType].examples.add(problem.typeName);
99
+ }
100
+ }
101
+ }
102
+
103
+ const commonUnresolvedTypes = Object.entries(unresolvedTypes)
104
+ .map(([type, data]) => ({
105
+ type,
106
+ count: data.count,
107
+ namespaces: Array.from(data.namespaces),
108
+ sourceModules: Array.from(data.sourceModules),
109
+ }))
110
+ .sort((a, b) => b.count - a.count)
111
+ .slice(0, 20);
112
+
113
+ const commonTypeConflicts = Object.entries(typeConflicts)
114
+ .map(([conflictType, data]) => ({
115
+ conflictType,
116
+ count: data.count,
117
+ examples: Array.from(data.examples).slice(0, 5),
118
+ }))
119
+ .sort((a, b) => b.count - a.count);
120
+
121
+ const problematicNamespaces = Object.entries(namespaceProblems)
122
+ .map(([namespace, data]) => ({
123
+ namespace,
124
+ problems: data.count,
125
+ types: Array.from(data.types).slice(0, 10),
126
+ }))
127
+ .sort((a, b) => b.problems - a.problems)
128
+ .slice(0, 10);
129
+
130
+ const mostProblematicModules = Object.entries(byModule)
131
+ .sort(([, a], [, b]) => b - a)
132
+ .slice(0, 10)
133
+ .map(([module, count]) => ({ module, count }));
134
+
135
+ const endTime = new Date();
136
+
137
+ return {
138
+ bySeverity,
139
+ byCategory,
140
+ byModule,
141
+ totalProblems: problems.length,
142
+ mostProblematicModules,
143
+ typeStatistics: { commonUnresolvedTypes, commonTypeConflicts, problematicNamespaces },
144
+ startTime,
145
+ endTime,
146
+ durationMs: endTime.getTime() - startTime.getTime(),
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Generate report summary with key issues and recommendations.
152
+ * Shared between ConsoleReporter and ReporterService.
153
+ */
154
+ export function generateReportSummary(statistics: ReportStatistics): GenerationReport["summary"] {
155
+ const { bySeverity, byCategory, totalProblems } = statistics;
156
+
157
+ const status = determineGenerationStatus(bySeverity, byCategory);
158
+
159
+ const keyIssues: string[] = [];
160
+ const recommendations: string[] = [];
161
+
162
+ if (byCategory[ProblemCategory.TYPE_RESOLUTION] > 0) {
163
+ keyIssues.push(
164
+ `${byCategory[ProblemCategory.TYPE_RESOLUTION]} type resolution warnings (produce 'never' type in output)`,
165
+ );
166
+ recommendations.push(
167
+ "Unresolved types produce 'never' in output — these are typically non-introspectable types or missing GIR dependencies",
168
+ );
169
+ }
170
+
171
+ if (byCategory[ProblemCategory.PARSING_FAILURE] > 0) {
172
+ keyIssues.push(`${byCategory[ProblemCategory.PARSING_FAILURE]} parsing failures occurred`);
173
+ recommendations.push("Check GIR file syntax and ensure proper introspection data");
174
+ }
175
+
176
+ if (byCategory[ProblemCategory.GENERATION_FAILURE] > 0) {
177
+ keyIssues.push(`${byCategory[ProblemCategory.GENERATION_FAILURE]} generation failures encountered`);
178
+ recommendations.push("Review template configuration and output settings");
179
+ }
180
+
181
+ if (byCategory[ProblemCategory.TYPE_CONFLICT] > 0) {
182
+ keyIssues.push(`${byCategory[ProblemCategory.TYPE_CONFLICT]} type conflicts detected`);
183
+ recommendations.push(
184
+ "Type conflicts are handled automatically — conflicting members are omitted or use union types",
185
+ );
186
+ }
187
+
188
+ if (keyIssues.length === 0 && totalProblems > 0) {
189
+ keyIssues.push(`${totalProblems} minor issues detected`);
190
+ }
191
+
192
+ if (recommendations.length === 0 && totalProblems > 0) {
193
+ recommendations.push("Review detailed problem list for specific improvement opportunities");
194
+ }
195
+
196
+ return { status, keyIssues, recommendations };
197
+ }
198
+
199
+ /**
200
+ * Group problems by category.
201
+ */
202
+ export function groupProblemsByCategory(problems: ProblemEntry[]): Record<ProblemCategory, ProblemEntry[]> {
203
+ const result = Object.values(ProblemCategory).reduce(
204
+ (acc, category) => {
205
+ acc[category] = [];
206
+ return acc;
207
+ },
208
+ {} as Record<ProblemCategory, ProblemEntry[]>,
209
+ );
210
+
211
+ for (const problem of problems) {
212
+ result[problem.category].push(problem);
213
+ }
214
+
215
+ return result;
216
+ }
2
217
 
3
218
  /**
4
219
  * Aggregated problem statistics by type
5
220
  */
6
221
  export interface ProblemTypeStatistics {
7
222
  /** Most common unresolved types */
8
- commonUnresolvedTypes: Array<{ type: string; count: number; namespaces: string[] }>;
223
+ commonUnresolvedTypes: Array<{ type: string; count: number; namespaces: string[]; sourceModules: string[] }>;
9
224
  /** Most common type conflicts */
10
225
  commonTypeConflicts: Array<{ conflictType: string; count: number; examples: string[] }>;
11
226
  /** Namespaces with most problems */
@@ -54,7 +269,7 @@ export interface GenerationReport {
54
269
  /** Summary and recommendations */
55
270
  summary: {
56
271
  /** Overall generation status */
57
- status: "success" | "partial";
272
+ status: GenerationStatus;
58
273
  /** Key issues summary */
59
274
  keyIssues: string[];
60
275
  /** Recommendations for fixing issues */