jsdoczoom 0.1.0 → 0.2.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.
@@ -9,10 +9,10 @@
9
9
  */
10
10
  /** Markdown guidance text for each validation status category */
11
11
  export const GUIDANCE = {
12
- syntax_error: `### Syntax error
12
+ syntax_error: `### Syntax error
13
13
 
14
14
  The file has TypeScript parse errors that prevent analysis. Fix syntax errors first — no JSDoc validation can run until the file parses cleanly.`,
15
- missing_jsdoc: `### Missing file-level JSDoc
15
+ missing_jsdoc: `### Missing file-level JSDoc
16
16
 
17
17
  Add a \`/** ... */\` block before the first code statement (after imports). This block is what jsdoczoom reads for orientation and validation.
18
18
 
@@ -27,7 +27,7 @@ import { resolve } from "node:path";
27
27
 
28
28
  export function example() { ... }
29
29
  \`\`\``,
30
- missing_summary: `### The @summary tag
30
+ missing_summary: `### The @summary tag
31
31
 
32
32
  The \`@summary\` tag provides a one-line overview — the first thing someone sees when scanning with jsdoczoom at the shallowest depth.
33
33
 
@@ -48,17 +48,35 @@ The \`@summary\` tag provides a one-line overview — the first thing someone se
48
48
  - \`@summary This file contains utility functions\` — says what it *contains*, not what it *does*
49
49
  - \`@summary Helpers\` — too vague, no domain context
50
50
  - \`@summary The main module\` — no information about purpose or scope`,
51
- multiple_summary: `### Multiple @summary tags
51
+ multiple_summary: `### Multiple @summary tags
52
52
 
53
53
  Each file must have exactly one \`@summary\` tag. Remove the extra \`@summary\` tags and keep a single one-line overview.`,
54
- missing_barrel: `### Missing barrel file
54
+ missing_barrel: `### Missing barrel file
55
55
 
56
56
  Directories with more than 3 TypeScript files should have an \`index.ts\` barrel file. The barrel provides a module-level \`@summary\` and description that helps agents understand what the directory contains before drilling into individual files.
57
57
 
58
58
  Create an \`index.ts\` that re-exports the directory's public API and add a file-level JSDoc block describing the module's capabilities.`,
59
- missing_description: `### The description paragraph
59
+ missing_description: `### The description paragraph
60
60
 
61
- The description is prose that appears before any \`@\` tags. It provides the deeper context that the summary cannot responsibilities, invariants, trade-offs, and failure modes.
61
+ The description is prose that **must appear before the first \`@\` tag** in the file-level JSDoc block. Text placed after tags is not recognized as description content.
62
+
63
+ **Correct ordering:**
64
+ \`\`\`typescript
65
+ /**
66
+ * Description paragraph goes here, before any tags.
67
+ *
68
+ * @summary One-line overview
69
+ */
70
+ \`\`\`
71
+
72
+ **Incorrect ordering (description will not be detected):**
73
+ \`\`\`typescript
74
+ /**
75
+ * @summary One-line overview
76
+ *
77
+ * Description paragraph after the tag — this will NOT be recognized.
78
+ */
79
+ \`\`\`
62
80
 
63
81
  **Good descriptions:**
64
82
  - Explain *why* this file exists and what problem it solves
@@ -241,4 +259,467 @@ import { globSync } from "glob";
241
259
  export function discoverFiles(...) { ... }
242
260
  \`\`\`
243
261
 
262
+ ### Common lint rules and examples
263
+
264
+ These rules are enforced in lint mode (\`-l\`). Understanding what passes and fails reduces trial-and-rerun cycles.
265
+
266
+ #### \`jsdoc/informative-docs\` — descriptions must add meaning
267
+
268
+ This rule rejects descriptions that merely restate the parameter name, type, or containing symbol name. Descriptions must provide behavioral context.
269
+
270
+ **Fails:**
271
+ - \`@param id - The id\`
272
+ - \`@param options - The options object\`
273
+ - \`@returns The result\`
274
+ - \`@param name - The name string\`
275
+
276
+ **Passes:**
277
+ - \`@param id - Unique identifier used for cache lookup and deduplication\`
278
+ - \`@param options - Controls retry behavior, timeout, and error handling strategy\`
279
+ - \`@returns Parsed configuration with defaults applied for missing fields\`
280
+ - \`@param name - Display name shown in the navigation sidebar\`
281
+
282
+ **Rule of thumb:** If removing the parameter name from the description leaves no useful information, the description is not informative enough.
283
+
284
+ #### \`jsdoc/check-tag-names\` — allowed tags only
285
+
286
+ The lint configuration rejects non-standard JSDoc tags. Common tags to **avoid in JSDoc blocks**:
287
+ - \`@remarks\` — move content to the description paragraph (prose before tags)
288
+ - \`@packageDocumentation\` — use \`@module\` instead
289
+ - \`@concept\`, \`@constraint\` — move content to the description paragraph
290
+
291
+ Framework directives that look like tags (e.g., \`@vitest-environment\`) should use plain comments instead:
292
+ \`\`\`typescript
293
+ // @vitest-environment node ← correct (plain comment)
294
+ /** @vitest-environment node */ ← incorrect (treated as JSDoc tag)
295
+ \`\`\`
296
+
297
+ #### \`jsdoc/require-throws\` — document throw conditions
298
+
299
+ Include \`@throws\` for any function that can throw, including catch-and-rethrow patterns:
300
+ \`\`\`typescript
301
+ /**
302
+ * @param path - File path to read
303
+ * @returns Parsed configuration object
304
+ * @throws {ConfigError} When the file is missing or contains invalid YAML
305
+ */
306
+ \`\`\`
307
+
308
+ If a function catches errors and rethrows them (wrapped or unwrapped), it still needs \`@throws\`.
309
+
310
+ ### Nested object parameters
311
+
312
+ When a function accepts an inline object parameter, document each property with a nested \`@param\` tag:
313
+
314
+ \`\`\`typescript
315
+ /**
316
+ * Create a new user account.
317
+ *
318
+ * @param data - Account creation payload
319
+ * @param data.email - Email address used for login and notifications
320
+ * @param data.displayName - Public-facing name shown in the UI
321
+ * @param data.role - Initial permission level assigned to the account
322
+ * @returns The created user record with generated ID
323
+ */
324
+ function createUser(data: { email: string; displayName: string; role: Role }): User {
325
+ \`\`\`
326
+
327
+ For React component props, use \`props\` (or \`root0\` if destructured) as the root:
328
+
329
+ \`\`\`typescript
330
+ /**
331
+ * @param props - Component properties
332
+ * @param props.title - Page heading displayed at the top
333
+ * @param props.onSubmit - Callback invoked when the form is submitted
334
+ */
335
+ function MyComponent(props: { title: string; onSubmit: () => void }) {
336
+ \`\`\`
337
+
338
+ ### Overload documentation
339
+
340
+ When a function has TypeScript overload signatures, document **both** the overload declarations and the implementation signature:
341
+
342
+ \`\`\`typescript
343
+ /**
344
+ * Parse a value from a string representation.
345
+ *
346
+ * @param input - Raw string to parse
347
+ * @returns Parsed numeric value
348
+ */
349
+ function parse(input: string): number;
350
+ /**
351
+ * Parse a value from a buffer.
352
+ *
353
+ * @param input - Binary buffer to parse
354
+ * @returns Parsed numeric value
355
+ */
356
+ function parse(input: Buffer): number;
357
+ /**
358
+ * Parse a value from string or buffer input. String inputs are decoded
359
+ * as UTF-8 before numeric parsing.
360
+ *
361
+ * @param input - String or buffer to parse
362
+ * @returns Parsed numeric value
363
+ * @throws {ParseError} When the input cannot be interpreted as a number
364
+ */
365
+ function parse(input: string | Buffer): number {
366
+ // implementation
367
+ }
368
+ \`\`\`
369
+
244
370
  `;
371
+ /** Explanation text for each lint rule, used by --explain-rule */
372
+ export const RULE_EXPLANATIONS = {
373
+ "jsdoc/require-jsdoc": `## jsdoc/require-jsdoc
374
+
375
+ Requires JSDoc blocks on all public (exported) declarations: functions, classes, methods, and named exports.
376
+
377
+ **Triggers on:**
378
+ \`\`\`typescript
379
+ export function process(data: Data): Result { ... } // missing JSDoc
380
+ \`\`\`
381
+
382
+ **Passes with:**
383
+ \`\`\`typescript
384
+ /**
385
+ * Transform raw data into a normalized result.
386
+ *
387
+ * @param data - Input payload to process
388
+ * @returns Normalized result with defaults applied
389
+ */
390
+ export function process(data: Data): Result { ... }
391
+ \`\`\`
392
+
393
+ Non-exported (private/internal) declarations do not require JSDoc.
394
+ `,
395
+ "jsdoc/require-param": `## jsdoc/require-param
396
+
397
+ Requires an \`@param\` tag for every parameter of a documented function.
398
+
399
+ **Triggers on:**
400
+ \`\`\`typescript
401
+ /**
402
+ * Load a configuration file.
403
+ */
404
+ function loadConfig(path: string, strict: boolean): Config { ... }
405
+ // Missing @param path and @param strict
406
+ \`\`\`
407
+
408
+ **Passes with:**
409
+ \`\`\`typescript
410
+ /**
411
+ * Load a configuration file.
412
+ *
413
+ * @param path - Absolute path to the YAML config file
414
+ * @param strict - When true, unknown keys cause a validation error
415
+ */
416
+ function loadConfig(path: string, strict: boolean): Config { ... }
417
+ \`\`\`
418
+
419
+ For inline object parameters, nested properties also require tags:
420
+ \`\`\`typescript
421
+ /**
422
+ * @param options - Request options
423
+ * @param options.timeout - Max wait time in milliseconds
424
+ * @param options.retries - Number of retry attempts on failure
425
+ */
426
+ \`\`\`
427
+ `,
428
+ "jsdoc/require-param-description": `## jsdoc/require-param-description
429
+
430
+ Requires every \`@param\` tag to include a description, not just the parameter name.
431
+
432
+ **Triggers on:**
433
+ \`\`\`typescript
434
+ /** @param path */
435
+ \`\`\`
436
+
437
+ **Passes with:**
438
+ \`\`\`typescript
439
+ /** @param path - Absolute filesystem path to the input file */
440
+ \`\`\`
441
+ `,
442
+ "jsdoc/require-returns": `## jsdoc/require-returns
443
+
444
+ Requires an \`@returns\` tag for functions that return a value (non-void).
445
+
446
+ **Triggers on:**
447
+ \`\`\`typescript
448
+ /**
449
+ * Parse the input string.
450
+ *
451
+ * @param input - Raw input text
452
+ */
453
+ function parse(input: string): ParsedResult { ... }
454
+ // Missing @returns
455
+ \`\`\`
456
+
457
+ **Passes with:**
458
+ \`\`\`typescript
459
+ /**
460
+ * Parse the input string.
461
+ *
462
+ * @param input - Raw input text
463
+ * @returns Parsed result with extracted tokens and metadata
464
+ */
465
+ function parse(input: string): ParsedResult { ... }
466
+ \`\`\`
467
+ `,
468
+ "jsdoc/require-returns-description": `## jsdoc/require-returns-description
469
+
470
+ Requires every \`@returns\` tag to include a description.
471
+
472
+ **Triggers on:**
473
+ \`\`\`typescript
474
+ /** @returns */
475
+ \`\`\`
476
+
477
+ **Passes with:**
478
+ \`\`\`typescript
479
+ /** @returns The normalized configuration with defaults applied */
480
+ \`\`\`
481
+ `,
482
+ "jsdoc/require-throws": `## jsdoc/require-throws
483
+
484
+ Requires \`@throws\` tags for functions that contain throw statements.
485
+
486
+ **Triggers on:**
487
+ \`\`\`typescript
488
+ /**
489
+ * Read a config file.
490
+ *
491
+ * @param path - File path
492
+ * @returns Parsed config
493
+ */
494
+ function readConfig(path: string): Config {
495
+ if (!existsSync(path)) throw new Error("not found");
496
+ // ...
497
+ }
498
+ // Missing @throws
499
+ \`\`\`
500
+
501
+ **Passes with:**
502
+ \`\`\`typescript
503
+ /**
504
+ * Read a config file.
505
+ *
506
+ * @param path - File path
507
+ * @returns Parsed config
508
+ * @throws {Error} When the file does not exist or contains invalid syntax
509
+ */
510
+ \`\`\`
511
+
512
+ Functions that catch and rethrow errors also need \`@throws\`.
513
+ `,
514
+ "jsdoc/check-param-names": `## jsdoc/check-param-names
515
+
516
+ Ensures \`@param\` tag names match actual parameter names and are in the correct order.
517
+
518
+ **Triggers on:**
519
+ \`\`\`typescript
520
+ /**
521
+ * @param name - User name
522
+ * @param id - User ID
523
+ */
524
+ function getUser(id: string, name: string) { ... }
525
+ // Parameters are in the wrong order
526
+ \`\`\`
527
+
528
+ **Passes with:**
529
+ \`\`\`typescript
530
+ /**
531
+ * @param id - User ID
532
+ * @param name - User name
533
+ */
534
+ function getUser(id: string, name: string) { ... }
535
+ \`\`\`
536
+ `,
537
+ "jsdoc/check-tag-names": `## jsdoc/check-tag-names
538
+
539
+ Rejects non-standard or unsupported JSDoc tags. Only standard JSDoc tags are allowed.
540
+
541
+ **Common invalid tags and fixes:**
542
+ - \`@remarks\` — move content to the description paragraph (prose before tags)
543
+ - \`@packageDocumentation\` — use \`@module\` instead
544
+ - \`@concept\`, \`@constraint\` — move content to the description paragraph
545
+ - \`@vitest-environment\` — use a plain comment: \`// @vitest-environment node\`
546
+
547
+ **Triggers on:**
548
+ \`\`\`typescript
549
+ /** @remarks This is important context. */
550
+ \`\`\`
551
+
552
+ **Passes with:**
553
+ \`\`\`typescript
554
+ /**
555
+ * This is important context.
556
+ *
557
+ * @summary Overview line
558
+ */
559
+ \`\`\`
560
+ `,
561
+ "jsdoc/no-types": `## jsdoc/no-types
562
+
563
+ Disallows type annotations in JSDoc tags. In TypeScript, types come from the type system.
564
+
565
+ **Triggers on:**
566
+ \`\`\`typescript
567
+ /** @param {string} name - The user name */
568
+ \`\`\`
569
+
570
+ **Passes with:**
571
+ \`\`\`typescript
572
+ /** @param name - The user name */
573
+ \`\`\`
574
+ `,
575
+ "jsdoc/informative-docs": `## jsdoc/informative-docs
576
+
577
+ Rejects JSDoc descriptions that merely restate the symbol name, type, or parameter name without adding meaning.
578
+
579
+ **Triggers on (not informative):**
580
+ - \`@param id - The id\`
581
+ - \`@param options - The options object\`
582
+ - \`@returns The result\`
583
+ - \`@param name - The name string\`
584
+ - \`@param data - Data to process\`
585
+
586
+ **Passes with (adds behavioral context):**
587
+ - \`@param id - Unique identifier used for cache lookup and deduplication\`
588
+ - \`@param options - Controls retry behavior, timeout, and error handling strategy\`
589
+ - \`@returns Parsed configuration with defaults applied for missing fields\`
590
+ - \`@param name - Display name shown in the navigation sidebar\`
591
+ - \`@param data - Raw sensor payload including timestamp and device metadata\`
592
+
593
+ **Rule of thumb:** If removing the parameter name from the description leaves no useful information, the description is not informative enough.
594
+ `,
595
+ "jsdoc/no-blank-blocks": `## jsdoc/no-blank-blocks
596
+
597
+ Disallows empty JSDoc blocks with no content.
598
+
599
+ **Triggers on:**
600
+ \`\`\`typescript
601
+ /** */
602
+ function doSomething() { ... }
603
+ \`\`\`
604
+
605
+ **Passes with:**
606
+ \`\`\`typescript
607
+ /**
608
+ * Execute the primary processing pipeline.
609
+ */
610
+ function doSomething() { ... }
611
+ \`\`\`
612
+ `,
613
+ "jsdoc/require-description": `## jsdoc/require-description
614
+
615
+ Requires a text description in every JSDoc block (not just tags).
616
+
617
+ **Triggers on:**
618
+ \`\`\`typescript
619
+ /**
620
+ * @param path - File path
621
+ * @returns Config object
622
+ */
623
+ function loadConfig(path: string): Config { ... }
624
+ // Has tags but no description text
625
+ \`\`\`
626
+
627
+ **Passes with:**
628
+ \`\`\`typescript
629
+ /**
630
+ * Load and parse a YAML configuration file with schema validation.
631
+ *
632
+ * @param path - File path
633
+ * @returns Config object
634
+ */
635
+ function loadConfig(path: string): Config { ... }
636
+ \`\`\`
637
+ `,
638
+ "jsdoczoom/require-file-jsdoc": `## jsdoczoom/require-file-jsdoc
639
+
640
+ Requires a file-level JSDoc block (\`/** ... */\`) before the first code statement.
641
+
642
+ **Triggers on:** Files with no JSDoc block before the first export, const, function, class, etc.
643
+
644
+ **Passes with:**
645
+ \`\`\`typescript
646
+ import { resolve } from "node:path";
647
+
648
+ /**
649
+ * Description of what this file does.
650
+ *
651
+ * @summary One-line overview
652
+ */
653
+
654
+ export function example() { ... }
655
+ \`\`\`
656
+ `,
657
+ "jsdoczoom/require-file-summary": `## jsdoczoom/require-file-summary
658
+
659
+ Requires exactly one \`@summary\` tag (exact lowercase) in the file-level JSDoc block.
660
+
661
+ **Important:** Only exact lowercase \`@summary\` is recognized. \`@Summary\`, \`@SUMMARY\`, and other case variants are ignored.
662
+
663
+ **Triggers on:**
664
+ - Files with no \`@summary\` tag in the file-level block
665
+ - Files with multiple \`@summary\` tags
666
+
667
+ **Passes with:**
668
+ \`\`\`typescript
669
+ /**
670
+ * Description of the module.
671
+ *
672
+ * @summary Concise one-line overview of what this file does
673
+ */
674
+ \`\`\`
675
+ `,
676
+ "jsdoczoom/require-file-description": `## jsdoczoom/require-file-description
677
+
678
+ Requires description content in the file-level JSDoc block. The description must appear as free-text before \`@\` tags, or use a description-synonym tag (\`@desc\`, \`@description\`, \`@file\`, \`@fileoverview\`).
679
+
680
+ **Triggers on:**
681
+ \`\`\`typescript
682
+ /** @summary Summary only */
683
+ export const x = 1;
684
+ // No description text before the tag
685
+ \`\`\`
686
+
687
+ **Passes with:**
688
+ \`\`\`typescript
689
+ /**
690
+ * This module handles user authentication and session management.
691
+ *
692
+ * @summary Auth module
693
+ */
694
+ \`\`\`
695
+
696
+ **Important:** Description text must appear **before** the first \`@\` tag. Text after tags is not recognized as description.
697
+ `,
698
+ "jsdoczoom/require-file-ordering": `## jsdoczoom/require-file-ordering
699
+
700
+ Warns when the file-level JSDoc block has structural issues:
701
+ 1. Multiple file-level JSDoc blocks before the first code statement
702
+ 2. Description text appearing after \`@\` tags instead of before them
703
+
704
+ **Correct ordering:**
705
+ \`\`\`typescript
706
+ /**
707
+ * Description paragraph first.
708
+ *
709
+ * More description if needed.
710
+ *
711
+ * @summary Overview line
712
+ * @module my-module
713
+ */
714
+ \`\`\`
715
+
716
+ **Incorrect (description after tags):**
717
+ \`\`\`typescript
718
+ /**
719
+ * @summary Overview line
720
+ *
721
+ * Description paragraph after tags — will trigger a warning.
722
+ */
723
+ \`\`\`
724
+ `,
725
+ };
@@ -29,73 +29,87 @@ import { JsdocError } from "./errors.js";
29
29
  * @throws {JsdocError} If the file cannot be read or parsed
30
30
  */
31
31
  export function generateTypeDeclarations(filePath) {
32
- let sourceText;
33
- try {
34
- sourceText = readFileSync(filePath, "utf-8");
35
- }
36
- catch (_error) {
37
- throw new JsdocError("FILE_NOT_FOUND", `Failed to read file: ${filePath}`);
38
- }
39
- // Create compiler options matching the project setup
40
- const compilerOptions = {
41
- declaration: true,
42
- emitDeclarationOnly: true,
43
- target: ts.ScriptTarget.Latest,
44
- module: ts.ModuleKind.NodeNext,
45
- moduleResolution: ts.ModuleResolutionKind.NodeNext,
46
- skipLibCheck: true,
47
- removeComments: false, // Preserve JSDoc comments
48
- };
49
- // Create a custom compiler host that provides our file
50
- const host = ts.createCompilerHost(compilerOptions);
51
- const originalGetSourceFile = host.getSourceFile;
52
- host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
53
- if (fileName === filePath) {
54
- return ts.createSourceFile(fileName, sourceText, languageVersion, true);
55
- }
56
- return originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
57
- };
58
- // Capture emitted output
59
- let declarationOutput = "";
60
- host.writeFile = (fileName, data) => {
61
- if (fileName.endsWith(".d.ts")) {
62
- declarationOutput = data;
63
- }
64
- };
65
- // Create program and emit declarations
66
- const program = ts.createProgram([filePath], compilerOptions, host);
67
- const sourceFile = program.getSourceFile(filePath);
68
- if (!sourceFile) {
69
- throw new JsdocError("PARSE_ERROR", `Failed to parse file: ${filePath}`);
70
- }
71
- const emitResult = program.emit(sourceFile, undefined, undefined, true);
72
- // Check for emit errors
73
- const diagnostics = ts
74
- .getPreEmitDiagnostics(program)
75
- .concat(emitResult.diagnostics);
76
- if (diagnostics.length > 0) {
77
- const errors = diagnostics
78
- .map((diagnostic) => {
79
- if (diagnostic.file && diagnostic.start !== undefined) {
80
- const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
81
- const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
82
- return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
83
- }
84
- return ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
85
- })
86
- .join("\n");
87
- throw new JsdocError("PARSE_ERROR", `TypeScript errors:\n${errors}`);
88
- }
89
- if (!declarationOutput) {
90
- // If no output was generated, the file may have no exports
91
- // Return empty string in this case
92
- return "";
93
- }
94
- // Clean up the output
95
- let cleaned = declarationOutput;
96
- // Remove empty export statement if present and no other exports
97
- if (cleaned.trim() === "export {};") {
98
- cleaned = "";
99
- }
100
- return cleaned;
32
+ let sourceText;
33
+ try {
34
+ sourceText = readFileSync(filePath, "utf-8");
35
+ } catch (_error) {
36
+ throw new JsdocError("FILE_NOT_FOUND", `Failed to read file: ${filePath}`);
37
+ }
38
+ // Create compiler options matching the project setup
39
+ const compilerOptions = {
40
+ declaration: true,
41
+ emitDeclarationOnly: true,
42
+ target: ts.ScriptTarget.Latest,
43
+ module: ts.ModuleKind.NodeNext,
44
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
45
+ skipLibCheck: true,
46
+ removeComments: false, // Preserve JSDoc comments
47
+ };
48
+ // Create a custom compiler host that provides our file
49
+ const host = ts.createCompilerHost(compilerOptions);
50
+ const originalGetSourceFile = host.getSourceFile;
51
+ host.getSourceFile = (
52
+ fileName,
53
+ languageVersion,
54
+ onError,
55
+ shouldCreateNewSourceFile,
56
+ ) => {
57
+ if (fileName === filePath) {
58
+ return ts.createSourceFile(fileName, sourceText, languageVersion, true);
59
+ }
60
+ return originalGetSourceFile.call(
61
+ host,
62
+ fileName,
63
+ languageVersion,
64
+ onError,
65
+ shouldCreateNewSourceFile,
66
+ );
67
+ };
68
+ // Capture emitted output
69
+ let declarationOutput = "";
70
+ host.writeFile = (fileName, data) => {
71
+ if (fileName.endsWith(".d.ts")) {
72
+ declarationOutput = data;
73
+ }
74
+ };
75
+ // Create program and emit declarations
76
+ const program = ts.createProgram([filePath], compilerOptions, host);
77
+ const sourceFile = program.getSourceFile(filePath);
78
+ if (!sourceFile) {
79
+ throw new JsdocError("PARSE_ERROR", `Failed to parse file: ${filePath}`);
80
+ }
81
+ const emitResult = program.emit(sourceFile, undefined, undefined, true);
82
+ // Check for emit errors
83
+ const diagnostics = ts
84
+ .getPreEmitDiagnostics(program)
85
+ .concat(emitResult.diagnostics);
86
+ if (diagnostics.length > 0) {
87
+ const errors = diagnostics
88
+ .map((diagnostic) => {
89
+ if (diagnostic.file && diagnostic.start !== undefined) {
90
+ const { line, character } =
91
+ diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
92
+ const message = ts.flattenDiagnosticMessageText(
93
+ diagnostic.messageText,
94
+ "\n",
95
+ );
96
+ return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
97
+ }
98
+ return ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
99
+ })
100
+ .join("\n");
101
+ throw new JsdocError("PARSE_ERROR", `TypeScript errors:\n${errors}`);
102
+ }
103
+ if (!declarationOutput) {
104
+ // If no output was generated, the file may have no exports
105
+ // Return empty string in this case
106
+ return "";
107
+ }
108
+ // Clean up the output
109
+ let cleaned = declarationOutput;
110
+ // Remove empty export statement if present and no other exports
111
+ if (cleaned.trim() === "export {};") {
112
+ cleaned = "";
113
+ }
114
+ return cleaned;
101
115
  }