@fuzdev/fuz_ui 0.194.0 → 0.195.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.
Files changed (61) hide show
  1. package/dist/DeclarationDetail.svelte +139 -64
  2. package/dist/DeclarationDetail.svelte.d.ts +9 -0
  3. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  4. package/dist/LibraryDetail.svelte +14 -10
  5. package/dist/LibraryDetail.svelte.d.ts +7 -0
  6. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  7. package/dist/declaration.svelte.d.ts +233 -25
  8. package/dist/declaration.svelte.d.ts.map +1 -1
  9. package/dist/declaration.svelte.js +71 -23
  10. package/dist/library.svelte.js +1 -1
  11. package/dist/library_gen.d.ts +24 -17
  12. package/dist/library_gen.d.ts.map +1 -1
  13. package/dist/library_gen.js +52 -32
  14. package/dist/library_output.d.ts +5 -4
  15. package/dist/library_output.d.ts.map +1 -1
  16. package/dist/library_output.js +11 -8
  17. package/dist/module.svelte.d.ts +21 -7
  18. package/dist/module.svelte.d.ts.map +1 -1
  19. package/dist/module.svelte.js +26 -11
  20. package/dist/tsdoc_mdz.d.ts +6 -1
  21. package/dist/tsdoc_mdz.d.ts.map +1 -1
  22. package/dist/tsdoc_mdz.js +23 -2
  23. package/package.json +10 -8
  24. package/src/lib/declaration.svelte.ts +90 -35
  25. package/src/lib/library.svelte.ts +1 -1
  26. package/src/lib/library_gen.ts +65 -42
  27. package/src/lib/library_output.ts +11 -8
  28. package/src/lib/module.svelte.ts +29 -16
  29. package/src/lib/tsdoc_mdz.ts +25 -2
  30. package/dist/analysis_context.d.ts +0 -199
  31. package/dist/analysis_context.d.ts.map +0 -1
  32. package/dist/analysis_context.js +0 -138
  33. package/dist/library_analysis.d.ts +0 -112
  34. package/dist/library_analysis.d.ts.map +0 -1
  35. package/dist/library_analysis.js +0 -106
  36. package/dist/library_generate.d.ts +0 -94
  37. package/dist/library_generate.d.ts.map +0 -1
  38. package/dist/library_generate.js +0 -147
  39. package/dist/library_pipeline.d.ts +0 -113
  40. package/dist/library_pipeline.d.ts.map +0 -1
  41. package/dist/library_pipeline.js +0 -160
  42. package/dist/module_helpers.d.ts +0 -334
  43. package/dist/module_helpers.d.ts.map +0 -1
  44. package/dist/module_helpers.js +0 -317
  45. package/dist/svelte_helpers.d.ts +0 -92
  46. package/dist/svelte_helpers.d.ts.map +0 -1
  47. package/dist/svelte_helpers.js +0 -367
  48. package/dist/ts_helpers.d.ts +0 -181
  49. package/dist/ts_helpers.d.ts.map +0 -1
  50. package/dist/ts_helpers.js +0 -674
  51. package/dist/tsdoc_helpers.d.ts +0 -119
  52. package/dist/tsdoc_helpers.d.ts.map +0 -1
  53. package/dist/tsdoc_helpers.js +0 -207
  54. package/src/lib/analysis_context.ts +0 -254
  55. package/src/lib/library_analysis.ts +0 -168
  56. package/src/lib/library_generate.ts +0 -215
  57. package/src/lib/library_pipeline.ts +0 -221
  58. package/src/lib/module_helpers.ts +0 -501
  59. package/src/lib/svelte_helpers.ts +0 -539
  60. package/src/lib/ts_helpers.ts +0 -862
  61. package/src/lib/tsdoc_helpers.ts +0 -246
@@ -1,674 +0,0 @@
1
- /**
2
- * TypeScript compiler API helpers for extracting metadata from source code.
3
- *
4
- * All functions are prefixed with `ts_` for clarity.
5
- *
6
- * @module
7
- */
8
- import ts from 'typescript';
9
- import { tsdoc_parse, tsdoc_apply_to_declaration, tsdoc_clean_comment } from './tsdoc_helpers.js';
10
- import { module_extract_dependencies, module_extract_path, module_is_source, } from './module_helpers.js';
11
- /**
12
- * Create TypeScript program for analysis.
13
- *
14
- * @param options - configuration options for program creation
15
- * @param log - optional logger for info messages
16
- * @returns the program and type checker
17
- * @throws Error if `tsconfig.json` is not found
18
- */
19
- export const ts_create_program = (options, log) => {
20
- const root = options?.root ?? './';
21
- const tsconfig_name = options?.tsconfig ?? 'tsconfig.json';
22
- const config_path = ts.findConfigFile(root, ts.sys.fileExists, tsconfig_name);
23
- if (!config_path) {
24
- throw new Error(`No ${tsconfig_name} found in ${root}`);
25
- }
26
- log?.info(`using ${config_path}`);
27
- const config_file = ts.readConfigFile(config_path, ts.sys.readFile);
28
- const parsed_config = ts.parseJsonConfigFileContent(config_file.config, ts.sys, root);
29
- // Merge compiler options if provided
30
- const compiler_options = options?.compiler_options
31
- ? { ...parsed_config.options, ...options.compiler_options }
32
- : parsed_config.options;
33
- const program = ts.createProgram(parsed_config.fileNames, compiler_options);
34
- return { program, checker: program.getTypeChecker() };
35
- };
36
- /**
37
- * Analyze a TypeScript file and extract module metadata.
38
- *
39
- * Wraps `ts_analyze_module_exports` and adds dependency information
40
- * from the source file info if available.
41
- *
42
- * This is a high-level function suitable for building documentation or library metadata.
43
- * For lower-level analysis, use `ts_analyze_module_exports` directly.
44
- *
45
- * @param source_file_info - the source file info (from Gro filer, file system, or other source)
46
- * @param ts_source_file - TypeScript source file from the program
47
- * @param module_path - the module path (relative to source root)
48
- * @param checker - TypeScript type checker
49
- * @param options - module source options for path extraction
50
- * @param ctx - analysis context for collecting diagnostics
51
- * @returns module metadata and re-export information
52
- */
53
- export const ts_analyze_module = (source_file_info, ts_source_file, module_path, checker, options, ctx) => {
54
- // Use the mid-level helper for core analysis
55
- const { module_comment, declarations, re_exports, star_exports } = ts_analyze_module_exports(ts_source_file, checker, options, ctx);
56
- // Extract dependencies and dependents if provided
57
- const { dependencies, dependents } = module_extract_dependencies(source_file_info, options);
58
- return {
59
- path: module_path,
60
- module_comment,
61
- declarations,
62
- dependencies,
63
- dependents,
64
- star_exports,
65
- re_exports,
66
- };
67
- };
68
- /**
69
- * Analyze all exports from a TypeScript source file.
70
- *
71
- * Extracts the module-level comment and all exported declarations with
72
- * complete metadata. Handles re-exports by:
73
- * - Same-name re-exports: tracked in `re_exports` for `also_exported_from` building
74
- * - Renamed re-exports: included as new declarations with `alias_of` metadata
75
- * - Star exports (`export * from`): tracked in `star_exports` for namespace-level info
76
- *
77
- * This is a mid-level function (above `ts_extract_*`, below `library_gen`)
78
- * suitable for building documentation, API explorers, or analysis tools.
79
- * For standard SvelteKit library layouts, use `module_create_source_options(process.cwd())`.
80
- *
81
- * @param source_file - the TypeScript source file to analyze
82
- * @param checker - the TypeScript type checker
83
- * @param options - module source options for path extraction in re-exports
84
- * @param ctx - analysis context for collecting diagnostics
85
- * @returns module comment, declarations, re-exports, and star exports
86
- */
87
- export const ts_analyze_module_exports = (source_file, checker, options, ctx) => {
88
- const declarations = [];
89
- const re_exports = [];
90
- const star_exports = [];
91
- // Extract module-level comment
92
- const module_comment = ts_extract_module_comment(source_file);
93
- // Extract star exports (export * from './module')
94
- for (const statement of source_file.statements) {
95
- if (ts.isExportDeclaration(statement) &&
96
- !statement.exportClause && // No exportClause means `export *`
97
- statement.moduleSpecifier &&
98
- ts.isStringLiteral(statement.moduleSpecifier)) {
99
- // Use the type checker to resolve the module - it has already resolved all imports
100
- // during program creation, so this leverages TypeScript's full module resolution
101
- const module_symbol = checker.getSymbolAtLocation(statement.moduleSpecifier);
102
- if (module_symbol) {
103
- // Get the source file from the module symbol's declarations
104
- const module_decl = module_symbol.valueDeclaration ?? module_symbol.declarations?.[0];
105
- if (module_decl) {
106
- const resolved_source = module_decl.getSourceFile();
107
- const resolved_path = resolved_source.fileName;
108
- // Only include star exports from source modules (not node_modules)
109
- if (module_is_source(resolved_path, options)) {
110
- star_exports.push(module_extract_path(resolved_path, options));
111
- }
112
- }
113
- }
114
- // If module couldn't be resolved (external package, etc.), skip it
115
- }
116
- }
117
- // Get all exported symbols
118
- const symbol = checker.getSymbolAtLocation(source_file);
119
- if (symbol) {
120
- const exports = checker.getExportsOfModule(symbol);
121
- for (const export_symbol of exports) {
122
- // Check if this is an alias (potential re-export) using the Alias flag
123
- const is_alias = (export_symbol.flags & ts.SymbolFlags.Alias) !== 0;
124
- if (is_alias) {
125
- // This might be a re-export - use getAliasedSymbol to find the original
126
- const aliased_symbol = checker.getAliasedSymbol(export_symbol);
127
- const aliased_decl = aliased_symbol.valueDeclaration || aliased_symbol.declarations?.[0];
128
- if (aliased_decl) {
129
- const original_source = aliased_decl.getSourceFile();
130
- // Check if this is a CROSS-FILE re-export (original in different file)
131
- if (original_source.fileName !== source_file.fileName) {
132
- // Only track if the original is from a source module (not node_modules)
133
- if (module_is_source(original_source.fileName, options)) {
134
- const original_module = module_extract_path(original_source.fileName, options);
135
- const original_name = aliased_symbol.name;
136
- const is_renamed = export_symbol.name !== original_name;
137
- if (is_renamed) {
138
- // Renamed re-export (export {foo as bar}) - create new declaration with alias_of
139
- const kind = ts_infer_declaration_kind(aliased_symbol, aliased_decl);
140
- const decl = {
141
- name: export_symbol.name,
142
- kind,
143
- alias_of: { module: original_module, name: original_name, kind },
144
- };
145
- // Renamed re-exports aren't nodocs - they're new declarations pointing to the original
146
- declarations.push({ declaration: decl, nodocs: false });
147
- }
148
- else {
149
- // Same-name re-export - track for also_exported_from, skip from declarations
150
- re_exports.push({
151
- name: export_symbol.name,
152
- original_module,
153
- });
154
- }
155
- continue;
156
- }
157
- // Re-export from external module (node_modules) - skip entirely
158
- continue;
159
- }
160
- // Within-file alias (export { x as y }) - fall through to normal analysis
161
- }
162
- }
163
- // Normal export or within-file alias - declared in this file
164
- const { declaration, nodocs } = ts_analyze_declaration(export_symbol, source_file, checker, ctx);
165
- // Include all declarations with nodocs flag - consumer decides filtering policy
166
- declarations.push({ declaration, nodocs });
167
- }
168
- }
169
- return {
170
- module_comment,
171
- declarations,
172
- re_exports,
173
- star_exports,
174
- };
175
- };
176
- /**
177
- * Analyze a TypeScript symbol and extract rich metadata.
178
- *
179
- * This is a high-level function that combines TSDoc parsing with TypeScript
180
- * type analysis to produce complete declaration metadata. Suitable for use
181
- * in documentation generators, IDE integrations, and other tooling.
182
- *
183
- * @param symbol - the TypeScript symbol to analyze
184
- * @param source_file - the source file containing the symbol
185
- * @param checker - the TypeScript type checker
186
- * @param ctx - optional analysis context for collecting diagnostics
187
- * @returns complete declaration metadata including docs, types, and parameters, plus nodocs flag
188
- */
189
- export const ts_analyze_declaration = (symbol, source_file, checker, ctx) => {
190
- const name = symbol.name;
191
- const decl_node = symbol.valueDeclaration || symbol.declarations?.[0];
192
- // Determine kind (fallback to 'variable' if no declaration node)
193
- const kind = decl_node ? ts_infer_declaration_kind(symbol, decl_node) : 'variable';
194
- const result = {
195
- name,
196
- kind,
197
- };
198
- if (!decl_node) {
199
- return { declaration: result, nodocs: false };
200
- }
201
- // Extract TSDoc
202
- const tsdoc = tsdoc_parse(decl_node, source_file);
203
- const nodocs = tsdoc?.nodocs ?? false;
204
- tsdoc_apply_to_declaration(result, tsdoc);
205
- // Extract source line
206
- const start = decl_node.getStart(source_file);
207
- const start_pos = source_file.getLineAndCharacterOfPosition(start);
208
- result.source_line = start_pos.line + 1;
209
- // Extract type-specific info
210
- if (result.kind === 'function') {
211
- ts_extract_function_info(decl_node, symbol, checker, result, tsdoc, ctx);
212
- }
213
- else if (result.kind === 'type') {
214
- ts_extract_type_info(decl_node, symbol, checker, result, ctx);
215
- }
216
- else if (result.kind === 'class') {
217
- ts_extract_class_info(decl_node, symbol, checker, result, ctx);
218
- }
219
- else if (result.kind === 'variable') {
220
- ts_extract_variable_info(decl_node, symbol, checker, result, ctx);
221
- }
222
- return { declaration: result, nodocs };
223
- };
224
- /**
225
- * Extract module-level comment.
226
- *
227
- * Requires `@module` tag to identify module comments. The tag line is stripped
228
- * from the output. Supports optional module renaming: `@module custom-name`.
229
- *
230
- * @see {@link https://typedoc.org/documents/Tags._module.html}
231
- */
232
- export const ts_extract_module_comment = (source_file) => {
233
- const full_text = source_file.getFullText();
234
- // Collect all JSDoc comments in the file
235
- const all_comments = [];
236
- // Check for comments at the start of the file (before any statements)
237
- const leading_comments = ts.getLeadingCommentRanges(full_text, 0);
238
- if (leading_comments?.length) {
239
- all_comments.push(...leading_comments);
240
- }
241
- // Check for comments before each statement
242
- for (const statement of source_file.statements) {
243
- const comments = ts.getLeadingCommentRanges(full_text, statement.getFullStart());
244
- if (comments?.length) {
245
- all_comments.push(...comments);
246
- }
247
- }
248
- // Find the first comment with `@module` tag
249
- for (const comment of all_comments) {
250
- const comment_text = full_text.substring(comment.pos, comment.end);
251
- if (!comment_text.trimStart().startsWith('/**'))
252
- continue;
253
- // Clean the comment first, then check for tag at start of line
254
- const cleaned = tsdoc_clean_comment(comment_text);
255
- if (!cleaned)
256
- continue;
257
- // Check for `@module` as a proper tag (at start of line, not mentioned in prose)
258
- if (/(?:^|\n)@module\b/.test(cleaned)) {
259
- const stripped = tsdoc_strip_module_tag(cleaned);
260
- return stripped || undefined;
261
- }
262
- }
263
- return undefined;
264
- };
265
- /**
266
- * Strip `@module` tag line from comment text.
267
- *
268
- * Handles formats:
269
- * - `@module` (standalone)
270
- * - `@module module-name` (with rename)
271
- */
272
- const tsdoc_strip_module_tag = (text) => {
273
- // Remove lines that START with `@module` (not mentioned in prose)
274
- const lines = text.split('\n');
275
- const filtered = lines.filter((line) => !/^\s*@module\b/.test(line));
276
- return filtered.join('\n').trim();
277
- };
278
- /**
279
- * Infer declaration kind from symbol and node.
280
- *
281
- * Maps TypeScript constructs to `DeclarationKind`:
282
- * - Classes → `'class'`
283
- * - Functions (declarations, expressions, arrows) → `'function'`
284
- * - Interfaces, type aliases → `'type'`
285
- * - Enums (regular and const) → `'type'`
286
- * - Variables → `'variable'` (unless function-valued → `'function'`)
287
- */
288
- export const ts_infer_declaration_kind = (symbol, node) => {
289
- // Check symbol flags
290
- if (symbol.flags & ts.SymbolFlags.Class)
291
- return 'class';
292
- if (symbol.flags & ts.SymbolFlags.Function)
293
- return 'function';
294
- if (symbol.flags & ts.SymbolFlags.Interface)
295
- return 'type';
296
- if (symbol.flags & ts.SymbolFlags.TypeAlias)
297
- return 'type';
298
- // Enums are treated as types (they define a named type with values)
299
- if (symbol.flags & ts.SymbolFlags.Enum)
300
- return 'type';
301
- if (symbol.flags & ts.SymbolFlags.ConstEnum)
302
- return 'type';
303
- // Check node kind
304
- if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node))
305
- return 'function';
306
- if (ts.isClassDeclaration(node))
307
- return 'class';
308
- if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node))
309
- return 'type';
310
- if (ts.isEnumDeclaration(node))
311
- return 'type';
312
- if (ts.isVariableDeclaration(node)) {
313
- // Check if it's a function-valued variable
314
- const init = node.initializer;
315
- if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
316
- return 'function';
317
- }
318
- return 'variable';
319
- }
320
- return 'variable';
321
- };
322
- /**
323
- * Extract parameters from a TypeScript signature with TSDoc descriptions and default values.
324
- *
325
- * Shared helper for extracting parameter information from both standalone functions
326
- * and class methods/constructors.
327
- *
328
- * @param sig - the TypeScript signature to extract parameters from
329
- * @param checker - TypeScript type checker for type resolution
330
- * @param tsdoc_params - map of parameter names to TSDoc descriptions (from tsdoc.params)
331
- * @returns array of parameter info objects
332
- */
333
- export const ts_extract_signature_parameters = (sig, checker, tsdoc_params) => {
334
- return sig.parameters.map((param) => {
335
- const param_decl = param.valueDeclaration;
336
- // Get type - use declaration location if available, otherwise get declared type
337
- let type_string = 'unknown';
338
- if (param_decl) {
339
- const param_type = checker.getTypeOfSymbolAtLocation(param, param_decl);
340
- type_string = checker.typeToString(param_type);
341
- }
342
- else {
343
- const param_type = checker.getDeclaredTypeOfSymbol(param);
344
- type_string = checker.typeToString(param_type);
345
- }
346
- // Get TSDoc description for this parameter
347
- const description = tsdoc_params?.get(param.name);
348
- // Extract default value from AST
349
- let default_value;
350
- if (param_decl && ts.isParameter(param_decl) && param_decl.initializer) {
351
- default_value = param_decl.initializer.getText();
352
- }
353
- const optional = !!(param_decl && ts.isParameter(param_decl) && param_decl.questionToken);
354
- return {
355
- name: param.name,
356
- type: type_string,
357
- ...(optional && { optional }),
358
- description,
359
- default_value,
360
- };
361
- });
362
- };
363
- /**
364
- * Extract function/method information including parameters
365
- * with descriptions and default values.
366
- *
367
- * @internal Use `ts_analyze_declaration` for high-level analysis.
368
- * @mutates declaration - adds type_signature, return_type, return_description, throws, since, parameters, generic_params
369
- */
370
- export const ts_extract_function_info = (node, symbol, checker, declaration, tsdoc, ctx) => {
371
- try {
372
- const type = checker.getTypeOfSymbolAtLocation(symbol, node);
373
- const signatures = type.getCallSignatures();
374
- if (signatures.length > 0) {
375
- const sig = signatures[0];
376
- declaration.type_signature = checker.signatureToString(sig);
377
- const return_type = checker.getReturnTypeOfSignature(sig);
378
- declaration.return_type = checker.typeToString(return_type);
379
- // Extract return description from TSDoc
380
- if (tsdoc?.returns) {
381
- declaration.return_description = tsdoc.returns;
382
- }
383
- // Extract throws and since from TSDoc
384
- if (tsdoc?.throws?.length) {
385
- declaration.throws = tsdoc.throws;
386
- }
387
- if (tsdoc?.since) {
388
- declaration.since = tsdoc.since;
389
- }
390
- // Extract parameters with descriptions and default values
391
- declaration.parameters = ts_extract_signature_parameters(sig, checker, tsdoc?.params);
392
- }
393
- }
394
- catch (err) {
395
- const loc = ts_get_node_location(node);
396
- ctx.add({
397
- kind: 'signature_analysis_failed',
398
- file: loc.file,
399
- line: loc.line,
400
- column: loc.column,
401
- message: `Failed to analyze signature for "${symbol.name}": ${err instanceof Error ? err.message : String(err)}`,
402
- severity: 'warning',
403
- function_name: symbol.name,
404
- });
405
- }
406
- // Extract generic type parameters
407
- if (ts.isFunctionDeclaration(node) || ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
408
- if (node.typeParameters?.length) {
409
- declaration.generic_params = node.typeParameters.map(ts_parse_generic_param);
410
- }
411
- }
412
- };
413
- /**
414
- * Extract type/interface information with rich property metadata.
415
- *
416
- * @internal Use `ts_analyze_declaration` for high-level analysis.
417
- * @mutates declaration - adds type_signature, generic_params, extends, properties
418
- */
419
- export const ts_extract_type_info = (node, _symbol, checker, declaration, ctx) => {
420
- try {
421
- const type = checker.getTypeAtLocation(node);
422
- declaration.type_signature = checker.typeToString(type);
423
- }
424
- catch (err) {
425
- const loc = ts_get_node_location(node);
426
- ctx.add({
427
- kind: 'type_extraction_failed',
428
- file: loc.file,
429
- line: loc.line,
430
- column: loc.column,
431
- message: `Failed to extract type for "${declaration.name}": ${err instanceof Error ? err.message : String(err)}`,
432
- severity: 'warning',
433
- symbol_name: declaration.name,
434
- });
435
- }
436
- if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) {
437
- if (node.typeParameters?.length) {
438
- declaration.generic_params = node.typeParameters.map(ts_parse_generic_param);
439
- }
440
- }
441
- if (ts.isInterfaceDeclaration(node)) {
442
- if (node.heritageClauses) {
443
- declaration.extends = node.heritageClauses
444
- .filter((hc) => hc.token === ts.SyntaxKind.ExtendsKeyword)
445
- .flatMap((hc) => hc.types.map((t) => t.getText()));
446
- }
447
- // Extract properties with full metadata
448
- declaration.properties = [];
449
- for (const member of node.members) {
450
- if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
451
- const prop_name = member.name.text;
452
- const prop_declaration = {
453
- name: prop_name,
454
- kind: 'variable',
455
- };
456
- // Extract modifiers
457
- const modifier_flags = ts_extract_modifiers(ts.getModifiers(member));
458
- if (modifier_flags.length > 0) {
459
- prop_declaration.modifiers = modifier_flags;
460
- }
461
- // Extract type
462
- if (member.type) {
463
- prop_declaration.type_signature = member.type.getText();
464
- }
465
- // Extract TSDoc
466
- const prop_tsdoc = tsdoc_parse(member, node.getSourceFile());
467
- if (prop_tsdoc) {
468
- prop_declaration.doc_comment = prop_tsdoc.text;
469
- }
470
- declaration.properties.push(prop_declaration);
471
- }
472
- }
473
- }
474
- };
475
- /**
476
- * Extract class information with rich member metadata.
477
- *
478
- * @internal Use `ts_analyze_declaration` for high-level analysis.
479
- * @mutates declaration - adds extends, implements, generic_params, members
480
- */
481
- export const ts_extract_class_info = (node, _symbol, checker, declaration, ctx) => {
482
- if (!ts.isClassDeclaration(node))
483
- return;
484
- if (node.heritageClauses) {
485
- declaration.extends = node.heritageClauses
486
- .filter((hc) => hc.token === ts.SyntaxKind.ExtendsKeyword)
487
- .flatMap((hc) => hc.types.map((t) => t.getText()));
488
- declaration.implements = node.heritageClauses
489
- .filter((hc) => hc.token === ts.SyntaxKind.ImplementsKeyword)
490
- .flatMap((hc) => hc.types.map((t) => t.getText()));
491
- }
492
- if (node.typeParameters?.length) {
493
- declaration.generic_params = node.typeParameters.map(ts_parse_generic_param);
494
- }
495
- // Extract members with full metadata
496
- declaration.members = [];
497
- for (const member of node.members) {
498
- if (ts.isPropertyDeclaration(member) ||
499
- ts.isMethodDeclaration(member) ||
500
- ts.isConstructorDeclaration(member)) {
501
- const is_constructor = ts.isConstructorDeclaration(member);
502
- const member_name = is_constructor
503
- ? 'constructor'
504
- : ts.isIdentifier(member.name)
505
- ? member.name.text
506
- : member.name.getText();
507
- if (!member_name)
508
- continue;
509
- // Skip private fields (those starting with #)
510
- if (member_name.startsWith('#'))
511
- continue;
512
- const member_kind = is_constructor
513
- ? 'constructor'
514
- : ts.isMethodDeclaration(member)
515
- ? 'function'
516
- : 'variable';
517
- const member_declaration = {
518
- name: member_name,
519
- kind: member_kind,
520
- };
521
- // Extract visibility and modifiers
522
- const modifier_flags = ts_extract_modifiers(ts.getModifiers(member));
523
- if (modifier_flags.length > 0) {
524
- member_declaration.modifiers = modifier_flags;
525
- }
526
- // Extract TSDoc
527
- const member_tsdoc = tsdoc_parse(member, node.getSourceFile());
528
- if (member_tsdoc) {
529
- member_declaration.doc_comment = member_tsdoc.text;
530
- }
531
- // Extract type information and parameters for methods and constructors
532
- try {
533
- if (ts.isPropertyDeclaration(member) && member.type) {
534
- member_declaration.type_signature = member.type.getText();
535
- }
536
- else if (ts.isMethodDeclaration(member) || ts.isConstructorDeclaration(member)) {
537
- let signatures = [];
538
- if (is_constructor) {
539
- // For constructors, get construct signatures from the class symbol
540
- // Skip anonymous classes (no name)
541
- if (node.name) {
542
- const class_symbol = checker.getSymbolAtLocation(node.name);
543
- if (class_symbol) {
544
- const class_type = checker.getTypeOfSymbolAtLocation(class_symbol, node);
545
- signatures = class_type.getConstructSignatures();
546
- }
547
- }
548
- }
549
- else {
550
- // For methods, get call signatures from the method symbol
551
- const member_symbol = checker.getSymbolAtLocation(member.name);
552
- if (member_symbol) {
553
- const member_type = checker.getTypeOfSymbolAtLocation(member_symbol, member);
554
- signatures = member_type.getCallSignatures();
555
- }
556
- }
557
- if (signatures.length > 0) {
558
- const sig = signatures[0];
559
- // Extract type signature for both constructors and methods
560
- member_declaration.type_signature = checker.signatureToString(sig);
561
- // For methods (but not constructors), also extract return info separately
562
- if (!is_constructor) {
563
- // Extract return type for methods
564
- const return_type = checker.getReturnTypeOfSignature(sig);
565
- member_declaration.return_type = checker.typeToString(return_type);
566
- // Extract return description from TSDoc
567
- if (member_tsdoc?.returns) {
568
- member_declaration.return_description = member_tsdoc.returns;
569
- }
570
- }
571
- // Extract parameters with descriptions and default values
572
- member_declaration.parameters = ts_extract_signature_parameters(sig, checker, member_tsdoc?.params);
573
- // Extract throws and since from TSDoc (for both methods and constructors)
574
- if (member_tsdoc?.throws?.length) {
575
- member_declaration.throws = member_tsdoc.throws;
576
- }
577
- if (member_tsdoc?.since) {
578
- member_declaration.since = member_tsdoc.since;
579
- }
580
- }
581
- }
582
- }
583
- catch (err) {
584
- const loc = ts_get_node_location(member);
585
- const class_name = node.name?.text ?? '<anonymous>';
586
- ctx.add({
587
- kind: 'class_member_failed',
588
- file: loc.file,
589
- line: loc.line,
590
- column: loc.column,
591
- message: `Failed to analyze member "${member_name}" in class "${class_name}": ${err instanceof Error ? err.message : String(err)}`,
592
- severity: 'warning',
593
- class_name,
594
- member_name,
595
- });
596
- }
597
- declaration.members.push(member_declaration);
598
- }
599
- }
600
- };
601
- /**
602
- * Extract variable information.
603
- *
604
- * @internal Use `ts_analyze_declaration` for high-level analysis.
605
- * @mutates declaration - adds type_signature
606
- */
607
- export const ts_extract_variable_info = (node, symbol, checker, declaration, ctx) => {
608
- try {
609
- const type = checker.getTypeOfSymbolAtLocation(symbol, node);
610
- declaration.type_signature = checker.typeToString(type);
611
- }
612
- catch (err) {
613
- const loc = ts_get_node_location(node);
614
- ctx.add({
615
- kind: 'type_extraction_failed',
616
- file: loc.file,
617
- line: loc.line,
618
- column: loc.column,
619
- message: `Failed to extract type for variable "${symbol.name}": ${err instanceof Error ? err.message : String(err)}`,
620
- severity: 'warning',
621
- symbol_name: symbol.name,
622
- });
623
- }
624
- };
625
- /**
626
- * Extract line and column from a TypeScript node.
627
- * Returns 1-based line and column numbers.
628
- */
629
- const ts_get_node_location = (node) => {
630
- const source_file = node.getSourceFile();
631
- const { line, character } = source_file.getLineAndCharacterOfPosition(node.getStart());
632
- return {
633
- file: source_file.fileName,
634
- line: line + 1, // Convert to 1-based
635
- column: character + 1, // Convert to 1-based
636
- };
637
- };
638
- const ts_parse_generic_param = (param) => {
639
- const result = {
640
- name: param.name.text,
641
- };
642
- if (param.constraint) {
643
- result.constraint = param.constraint.getText();
644
- }
645
- if (param.default) {
646
- result.default_type = param.default.getText();
647
- }
648
- return result;
649
- };
650
- /**
651
- * Extract modifier keywords from a node's modifiers.
652
- *
653
- * Returns an array of modifier strings like `['public', 'readonly', 'static']`.
654
- */
655
- const ts_extract_modifiers = (modifiers) => {
656
- const modifier_flags = [];
657
- if (!modifiers)
658
- return modifier_flags;
659
- for (const mod of modifiers) {
660
- if (mod.kind === ts.SyntaxKind.PublicKeyword)
661
- modifier_flags.push('public');
662
- else if (mod.kind === ts.SyntaxKind.PrivateKeyword)
663
- modifier_flags.push('private');
664
- else if (mod.kind === ts.SyntaxKind.ProtectedKeyword)
665
- modifier_flags.push('protected');
666
- else if (mod.kind === ts.SyntaxKind.ReadonlyKeyword)
667
- modifier_flags.push('readonly');
668
- else if (mod.kind === ts.SyntaxKind.StaticKeyword)
669
- modifier_flags.push('static');
670
- else if (mod.kind === ts.SyntaxKind.AbstractKeyword)
671
- modifier_flags.push('abstract');
672
- }
673
- return modifier_flags;
674
- };