@openwebf/webf 0.22.6 → 0.22.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/analyzer.js CHANGED
@@ -37,12 +37,15 @@ exports.analyzer = analyzer;
37
37
  exports.buildClassRelationship = buildClassRelationship;
38
38
  exports.clearCaches = clearCaches;
39
39
  const typescript_1 = __importStar(require("typescript"));
40
+ const tsdoc_1 = require("@microsoft/tsdoc");
40
41
  const declaration_1 = require("./declaration");
41
42
  const utils_1 = require("./utils");
42
43
  // Cache for parsed source files to avoid re-parsing
43
44
  const sourceFileCache = new Map();
44
45
  // Cache for type conversions to avoid redundant processing
45
46
  const typeConversionCache = new Map();
47
+ // TSDoc parser instance
48
+ const tsdocParser = new tsdoc_1.TSDocParser();
46
49
  // Type mapping constants for better performance
47
50
  const BASIC_TYPE_MAP = {
48
51
  [typescript_1.default.SyntaxKind.StringKeyword]: declaration_1.FunctionArgumentType.dom_string,
@@ -72,7 +75,7 @@ function analyzer(blob, definedPropertyCollector, unionTypeCollector) {
72
75
  blob.objects = sourceFile.statements
73
76
  .map(statement => {
74
77
  try {
75
- return walkProgram(blob, statement, definedPropertyCollector, unionTypeCollector);
78
+ return walkProgram(blob, statement, sourceFile, definedPropertyCollector, unionTypeCollector);
76
79
  }
77
80
  catch (error) {
78
81
  console.error(`Error processing statement in ${blob.source}:`, error);
@@ -106,6 +109,93 @@ function getInterfaceName(statement) {
106
109
  }
107
110
  return statement.name.escapedText;
108
111
  }
112
+ function getJSDocComment(node, sourceFile) {
113
+ const sourceText = sourceFile.getFullText();
114
+ const nodeStart = node.getFullStart();
115
+ const nodePos = node.getStart(sourceFile);
116
+ // Get the text between full start and actual start (includes leading trivia)
117
+ const leadingText = sourceText.substring(nodeStart, nodePos);
118
+ // Find JSDoc comment in the leading text
119
+ const jsDocMatch = leadingText.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
120
+ if (!jsDocMatch)
121
+ return undefined;
122
+ // Extract the full JSDoc comment including delimiters
123
+ const commentText = jsDocMatch[0];
124
+ const commentStartPos = nodeStart + leadingText.lastIndexOf(commentText);
125
+ // Create a TextRange for the comment
126
+ const textRange = tsdoc_1.TextRange.fromStringRange(sourceText, commentStartPos, commentStartPos + commentText.length);
127
+ // Parse the JSDoc comment using TSDoc
128
+ const parserContext = tsdocParser.parseRange(textRange);
129
+ const docComment = parserContext.docComment;
130
+ // For now, always use the raw comment to preserve all tags including @default
131
+ // TSDoc parser doesn't handle @default tags properly out of the box
132
+ // Fallback to raw comment if TSDoc parsing fails
133
+ const comment = jsDocMatch[1]
134
+ .split('\n')
135
+ .map(line => line.replace(/^\s*\*\s?/, ''))
136
+ .join('\n')
137
+ .trim();
138
+ return comment || undefined;
139
+ }
140
+ // Helper function to render TSDoc nodes to string
141
+ function renderDocNodes(nodes) {
142
+ return nodes.map(node => {
143
+ if (node.kind === 'PlainText') {
144
+ return node.text;
145
+ }
146
+ else if (node.kind === 'SoftBreak') {
147
+ return '\n';
148
+ }
149
+ else if (node.kind === 'Paragraph') {
150
+ return renderDocNodes(node.nodes);
151
+ }
152
+ return '';
153
+ }).join('').trim();
154
+ }
155
+ // Special function to get the first JSDoc comment in a file for the first interface
156
+ function getFirstInterfaceJSDoc(statement, sourceFile) {
157
+ // Find all interfaces in the file
158
+ const interfaces = [];
159
+ typescript_1.default.forEachChild(sourceFile, child => {
160
+ if (typescript_1.default.isInterfaceDeclaration(child)) {
161
+ interfaces.push(child);
162
+ }
163
+ });
164
+ // If this is the first interface, check for a file-level JSDoc
165
+ if (interfaces.length > 0 && interfaces[0] === statement) {
166
+ const sourceText = sourceFile.getFullText();
167
+ const firstInterfacePos = statement.getFullStart();
168
+ // Get all text before the first interface
169
+ const textBeforeInterface = sourceText.substring(0, firstInterfacePos);
170
+ // Find the last JSDoc comment before the interface
171
+ const jsDocMatches = textBeforeInterface.match(/\/\*\*([\s\S]*?)\*\//g);
172
+ if (jsDocMatches && jsDocMatches.length > 0) {
173
+ const lastJsDoc = jsDocMatches[jsDocMatches.length - 1];
174
+ const commentStartPos = textBeforeInterface.lastIndexOf(lastJsDoc);
175
+ // Create a TextRange for the comment
176
+ const textRange = tsdoc_1.TextRange.fromStringRange(sourceText, commentStartPos, commentStartPos + lastJsDoc.length);
177
+ // Parse the JSDoc comment using TSDoc
178
+ const parserContext = tsdocParser.parseRange(textRange);
179
+ const docComment = parserContext.docComment;
180
+ // Extract the parsed content
181
+ if (docComment.summarySection) {
182
+ const summary = renderDocNodes(docComment.summarySection.nodes);
183
+ if (summary)
184
+ return summary;
185
+ }
186
+ // Fallback to raw comment
187
+ const comment = lastJsDoc
188
+ .replace(/^\/\*\*/, '')
189
+ .replace(/\*\/$/, '')
190
+ .split('\n')
191
+ .map(line => line.replace(/^\s*\*\s?/, ''))
192
+ .join('\n')
193
+ .trim();
194
+ return comment || undefined;
195
+ }
196
+ }
197
+ return undefined;
198
+ }
109
199
  function getHeritageType(heritage) {
110
200
  if (!heritage.types.length)
111
201
  return null;
@@ -210,11 +300,15 @@ function getParameterBaseType(type, mode) {
210
300
  }
211
301
  }
212
302
  if (type.kind === typescript_1.default.SyntaxKind.LiteralType) {
213
- // Handle literal types - check if it's a null literal
303
+ // Handle literal types
214
304
  const literalType = type;
215
305
  if (literalType.literal.kind === typescript_1.default.SyntaxKind.NullKeyword) {
216
306
  return declaration_1.FunctionArgumentType.null;
217
307
  }
308
+ if (literalType.literal.kind === typescript_1.default.SyntaxKind.StringLiteral) {
309
+ // Return the string literal value itself
310
+ return literalType.literal.text;
311
+ }
218
312
  return declaration_1.FunctionArgumentType.any;
219
313
  }
220
314
  return declaration_1.FunctionArgumentType.any;
@@ -379,10 +473,10 @@ function isParamsReadOnly(m) {
379
473
  return false;
380
474
  return m.modifiers.some(k => k.kind === typescript_1.default.SyntaxKind.ReadonlyKeyword);
381
475
  }
382
- function walkProgram(blob, statement, definedPropertyCollector, unionTypeCollector) {
476
+ function walkProgram(blob, statement, sourceFile, definedPropertyCollector, unionTypeCollector) {
383
477
  switch (statement.kind) {
384
478
  case typescript_1.default.SyntaxKind.InterfaceDeclaration:
385
- return processInterfaceDeclaration(statement, blob, definedPropertyCollector, unionTypeCollector);
479
+ return processInterfaceDeclaration(statement, blob, sourceFile, definedPropertyCollector, unionTypeCollector);
386
480
  case typescript_1.default.SyntaxKind.VariableStatement:
387
481
  return processVariableStatement(statement, unionTypeCollector);
388
482
  case typescript_1.default.SyntaxKind.TypeAliasDeclaration:
@@ -399,9 +493,13 @@ function processTypeAliasDeclaration(statement, blob) {
399
493
  typeAlias.type = printer.printNode(typescript_1.default.EmitHint.Unspecified, statement.type, statement.getSourceFile());
400
494
  return typeAlias;
401
495
  }
402
- function processInterfaceDeclaration(statement, blob, definedPropertyCollector, unionTypeCollector) {
496
+ function processInterfaceDeclaration(statement, blob, sourceFile, definedPropertyCollector, unionTypeCollector) {
403
497
  const interfaceName = statement.name.escapedText.toString();
404
498
  const obj = new declaration_1.ClassObject();
499
+ // Capture JSDoc comment for the interface
500
+ const directComment = getJSDocComment(statement, sourceFile);
501
+ const fileComment = getFirstInterfaceJSDoc(statement, sourceFile);
502
+ obj.documentation = directComment || fileComment;
405
503
  // Process heritage clauses
406
504
  if (statement.heritageClauses) {
407
505
  const heritage = statement.heritageClauses[0];
@@ -420,40 +518,41 @@ function processInterfaceDeclaration(statement, blob, definedPropertyCollector,
420
518
  }
421
519
  // Process members in batches for better performance
422
520
  const members = Array.from(statement.members);
423
- processMembersBatch(members, obj, definedPropertyCollector, unionTypeCollector);
521
+ processMembersBatch(members, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
424
522
  declaration_1.ClassObject.globalClassMap[interfaceName] = obj;
425
523
  return obj;
426
524
  }
427
- function processMembersBatch(members, obj, definedPropertyCollector, unionTypeCollector) {
525
+ function processMembersBatch(members, obj, sourceFile, definedPropertyCollector, unionTypeCollector) {
428
526
  for (const member of members) {
429
527
  try {
430
- processMember(member, obj, definedPropertyCollector, unionTypeCollector);
528
+ processMember(member, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
431
529
  }
432
530
  catch (error) {
433
531
  console.error(`Error processing member:`, error);
434
532
  }
435
533
  }
436
534
  }
437
- function processMember(member, obj, definedPropertyCollector, unionTypeCollector) {
535
+ function processMember(member, obj, sourceFile, definedPropertyCollector, unionTypeCollector) {
438
536
  switch (member.kind) {
439
537
  case typescript_1.default.SyntaxKind.PropertySignature:
440
- processPropertySignature(member, obj, definedPropertyCollector, unionTypeCollector);
538
+ processPropertySignature(member, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
441
539
  break;
442
540
  case typescript_1.default.SyntaxKind.MethodSignature:
443
- processMethodSignature(member, obj, unionTypeCollector);
541
+ processMethodSignature(member, obj, sourceFile, unionTypeCollector);
444
542
  break;
445
543
  case typescript_1.default.SyntaxKind.IndexSignature:
446
- processIndexSignature(member, obj, unionTypeCollector);
544
+ processIndexSignature(member, obj, sourceFile, unionTypeCollector);
447
545
  break;
448
546
  case typescript_1.default.SyntaxKind.ConstructSignature:
449
- processConstructSignature(member, obj, unionTypeCollector);
547
+ processConstructSignature(member, obj, sourceFile, unionTypeCollector);
450
548
  break;
451
549
  }
452
550
  }
453
- function processPropertySignature(member, obj, definedPropertyCollector, unionTypeCollector) {
551
+ function processPropertySignature(member, obj, sourceFile, definedPropertyCollector, unionTypeCollector) {
454
552
  const prop = new declaration_1.PropsDeclaration();
455
553
  prop.name = getPropName(member.name, prop);
456
554
  prop.readonly = isParamsReadOnly(member);
555
+ prop.documentation = getJSDocComment(member, sourceFile);
457
556
  definedPropertyCollector.properties.add(prop.name);
458
557
  if (!member.type) {
459
558
  console.warn(`Property ${prop.name} has no type annotation`);
@@ -491,10 +590,11 @@ function createAsyncProperty(prop, mode) {
491
590
  };
492
591
  return asyncProp;
493
592
  }
494
- function processMethodSignature(member, obj, unionTypeCollector) {
593
+ function processMethodSignature(member, obj, sourceFile, unionTypeCollector) {
495
594
  var _a;
496
595
  const f = new declaration_1.FunctionDeclaration();
497
596
  f.name = getPropName(member.name);
597
+ f.documentation = getJSDocComment(member, sourceFile);
498
598
  f.args = member.parameters.map(params => paramsNodeToArguments(params, unionTypeCollector));
499
599
  if (member.type) {
500
600
  const mode = new declaration_1.ParameterMode();
@@ -531,7 +631,7 @@ function createAsyncMethod(member, originalFunc, unionTypeCollector) {
531
631
  };
532
632
  return asyncFunc;
533
633
  }
534
- function processIndexSignature(member, obj, unionTypeCollector) {
634
+ function processIndexSignature(member, obj, sourceFile, unionTypeCollector) {
535
635
  const prop = new declaration_1.IndexedPropertyDeclaration();
536
636
  const modifier = member.modifiers;
537
637
  prop.readonly = !!(modifier && modifier[0].kind === typescript_1.default.SyntaxKind.ReadonlyKeyword);
@@ -544,7 +644,7 @@ function processIndexSignature(member, obj, unionTypeCollector) {
544
644
  prop.typeMode = mode;
545
645
  obj.indexedProp = prop;
546
646
  }
547
- function processConstructSignature(member, obj, unionTypeCollector) {
647
+ function processConstructSignature(member, obj, sourceFile, unionTypeCollector) {
548
648
  const c = new declaration_1.FunctionDeclaration();
549
649
  c.name = 'constructor';
550
650
  c.args = member.parameters.map(params => paramsNodeToArguments(params, unionTypeCollector));
package/dist/react.js CHANGED
@@ -15,6 +15,9 @@ function readTemplate(name) {
15
15
  return fs_1.default.readFileSync(path_1.default.join(__dirname, '../templates/' + name + '.tpl'), { encoding: 'utf-8' });
16
16
  }
17
17
  function generateReturnType(type) {
18
+ if ((0, utils_1.isUnionType)(type)) {
19
+ return type.value.map(v => `'${v.value}'`).join(' | ');
20
+ }
18
21
  if ((0, utils_1.isPointerType)(type)) {
19
22
  const pointerType = (0, utils_1.getPointerType)(type);
20
23
  return pointerType;
@@ -79,6 +82,19 @@ function generateMethodDeclaration(method) {
79
82
  var returnType = generateReturnType(method.returnType);
80
83
  return `${methodName}(${args}): ${returnType};`;
81
84
  }
85
+ function generateMethodDeclarationWithDocs(method, indent = '') {
86
+ let result = '';
87
+ if (method.documentation) {
88
+ result += `${indent}/**\n`;
89
+ const docLines = method.documentation.split('\n');
90
+ docLines.forEach(line => {
91
+ result += `${indent} * ${line}\n`;
92
+ });
93
+ result += `${indent} */\n`;
94
+ }
95
+ result += `${indent}${generateMethodDeclaration(method)}`;
96
+ return result;
97
+ }
82
98
  function toReactEventName(name) {
83
99
  const eventName = 'on-' + name;
84
100
  return lodash_1.default.camelCase(eventName);
@@ -110,9 +126,13 @@ function generateReactComponent(blob, packageName, relativeDir) {
110
126
  const events = classObjects.filter(object => {
111
127
  return object.name.endsWith('Events');
112
128
  });
129
+ const methods = classObjects.filter(object => {
130
+ return object.name.endsWith('Methods');
131
+ });
113
132
  const others = classObjects.filter(object => {
114
133
  return !object.name.endsWith('Properties')
115
- && !object.name.endsWith('Events');
134
+ && !object.name.endsWith('Events')
135
+ && !object.name.endsWith('Methods');
116
136
  });
117
137
  // Include type aliases
118
138
  const typeAliasDeclarations = typeAliases.map(typeAlias => {
@@ -120,6 +140,19 @@ function generateReactComponent(blob, packageName, relativeDir) {
120
140
  }).join('\n');
121
141
  const dependencies = [
122
142
  typeAliasDeclarations,
143
+ // Include Methods interfaces as dependencies
144
+ methods.map(object => {
145
+ const methodDeclarations = object.methods.map(method => {
146
+ return generateMethodDeclarationWithDocs(method, ' ');
147
+ }).join('\n');
148
+ let interfaceDoc = '';
149
+ if (object.documentation) {
150
+ interfaceDoc = `/**\n${object.documentation.split('\n').map(line => ` * ${line}`).join('\n')}\n */\n`;
151
+ }
152
+ return `${interfaceDoc}interface ${object.name} {
153
+ ${methodDeclarations}
154
+ }`;
155
+ }).join('\n\n'),
123
156
  others.map(object => {
124
157
  const props = object.props.map(prop => {
125
158
  if (prop.optional) {
@@ -135,7 +168,7 @@ interface ${object.name} {
135
168
  ].filter(Boolean).join('\n\n');
136
169
  // Generate all components from this file
137
170
  const components = [];
138
- // Create a map of component names to their properties and events
171
+ // Create a map of component names to their properties, events, and methods
139
172
  const componentMap = new Map();
140
173
  // Process all Properties interfaces
141
174
  properties.forEach(prop => {
@@ -153,6 +186,14 @@ interface ${object.name} {
153
186
  }
154
187
  componentMap.get(componentName).events = event;
155
188
  });
189
+ // Process all Methods interfaces
190
+ methods.forEach(method => {
191
+ const componentName = method.name.replace(/Methods$/, '');
192
+ if (!componentMap.has(componentName)) {
193
+ componentMap.set(componentName, {});
194
+ }
195
+ componentMap.get(componentName).methods = method;
196
+ });
156
197
  // If we have multiple components, we need to generate a combined file
157
198
  const componentEntries = Array.from(componentMap.entries());
158
199
  if (componentEntries.length === 0) {
@@ -180,6 +221,7 @@ interface ${object.name} {
180
221
  className: className,
181
222
  properties: component.properties,
182
223
  events: component.events,
224
+ methods: component.methods,
183
225
  classObjectDictionary,
184
226
  dependencies,
185
227
  blob,
@@ -187,6 +229,7 @@ interface ${object.name} {
187
229
  toWebFTagName,
188
230
  generateReturnType,
189
231
  generateMethodDeclaration,
232
+ generateMethodDeclarationWithDocs,
190
233
  generateEventHandlerType,
191
234
  getEventType,
192
235
  });
@@ -214,6 +257,7 @@ interface ${object.name} {
214
257
  className: className,
215
258
  properties: component.properties,
216
259
  events: component.events,
260
+ methods: component.methods,
217
261
  classObjectDictionary,
218
262
  dependencies: '', // Dependencies will be at the top
219
263
  blob,
@@ -221,6 +265,7 @@ interface ${object.name} {
221
265
  toWebFTagName,
222
266
  generateReturnType,
223
267
  generateMethodDeclaration,
268
+ generateMethodDeclarationWithDocs,
224
269
  generateEventHandlerType,
225
270
  getEventType,
226
271
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwebf/webf",
3
- "version": "0.22.6",
3
+ "version": "0.22.8",
4
4
  "description": "Command line tools for WebF",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -36,6 +36,8 @@
36
36
  "ts-jest": "^29.1.2"
37
37
  },
38
38
  "dependencies": {
39
+ "@microsoft/tsdoc": "^0.15.1",
40
+ "@microsoft/tsdoc-config": "^0.17.1",
39
41
  "commander": "^14.0.0",
40
42
  "glob": "^10.3.10",
41
43
  "inquirer": "^8.2.6",
package/src/analyzer.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript';
2
+ import { TSDocParser, TextRange, DocComment } from '@microsoft/tsdoc';
2
3
  import {IDLBlob} from './IDLBlob';
3
4
  import {
4
5
  ClassObject,
@@ -30,6 +31,9 @@ const sourceFileCache = new Map<string, ts.SourceFile>();
30
31
  // Cache for type conversions to avoid redundant processing
31
32
  const typeConversionCache = new Map<string, ParameterType>();
32
33
 
34
+ // TSDoc parser instance
35
+ const tsdocParser = new TSDocParser();
36
+
33
37
  // Type mapping constants for better performance
34
38
  const BASIC_TYPE_MAP: Partial<Record<ts.SyntaxKind, FunctionArgumentType>> = {
35
39
  [ts.SyntaxKind.StringKeyword]: FunctionArgumentType.dom_string,
@@ -62,7 +66,7 @@ export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropert
62
66
  blob.objects = sourceFile.statements
63
67
  .map(statement => {
64
68
  try {
65
- return walkProgram(blob, statement, definedPropertyCollector, unionTypeCollector);
69
+ return walkProgram(blob, statement, sourceFile, definedPropertyCollector, unionTypeCollector);
66
70
  } catch (error) {
67
71
  console.error(`Error processing statement in ${blob.source}:`, error);
68
72
  return null;
@@ -98,6 +102,118 @@ function getInterfaceName(statement: ts.Statement): string {
98
102
  return statement.name.escapedText as string;
99
103
  }
100
104
 
105
+ function getJSDocComment(node: ts.Node, sourceFile: ts.SourceFile): string | undefined {
106
+
107
+ const sourceText = sourceFile.getFullText();
108
+ const nodeStart = node.getFullStart();
109
+ const nodePos = node.getStart(sourceFile);
110
+
111
+ // Get the text between full start and actual start (includes leading trivia)
112
+ const leadingText = sourceText.substring(nodeStart, nodePos);
113
+
114
+
115
+ // Find JSDoc comment in the leading text
116
+ const jsDocMatch = leadingText.match(/\/\*\*([\s\S]*?)\*\/\s*$/);
117
+ if (!jsDocMatch) return undefined;
118
+
119
+ // Extract the full JSDoc comment including delimiters
120
+ const commentText = jsDocMatch[0];
121
+ const commentStartPos = nodeStart + leadingText.lastIndexOf(commentText);
122
+
123
+ // Create a TextRange for the comment
124
+ const textRange = TextRange.fromStringRange(
125
+ sourceText,
126
+ commentStartPos,
127
+ commentStartPos + commentText.length
128
+ );
129
+
130
+ // Parse the JSDoc comment using TSDoc
131
+ const parserContext = tsdocParser.parseRange(textRange);
132
+ const docComment = parserContext.docComment;
133
+
134
+ // For now, always use the raw comment to preserve all tags including @default
135
+ // TSDoc parser doesn't handle @default tags properly out of the box
136
+
137
+ // Fallback to raw comment if TSDoc parsing fails
138
+ const comment = jsDocMatch[1]
139
+ .split('\n')
140
+ .map(line => line.replace(/^\s*\*\s?/, ''))
141
+ .join('\n')
142
+ .trim();
143
+ return comment || undefined;
144
+ }
145
+
146
+ // Helper function to render TSDoc nodes to string
147
+ function renderDocNodes(nodes: ReadonlyArray<any>): string {
148
+ return nodes.map(node => {
149
+ if (node.kind === 'PlainText') {
150
+ return node.text;
151
+ } else if (node.kind === 'SoftBreak') {
152
+ return '\n';
153
+ } else if (node.kind === 'Paragraph') {
154
+ return renderDocNodes(node.nodes);
155
+ }
156
+ return '';
157
+ }).join('').trim();
158
+ }
159
+
160
+ // Special function to get the first JSDoc comment in a file for the first interface
161
+ function getFirstInterfaceJSDoc(statement: ts.InterfaceDeclaration, sourceFile: ts.SourceFile): string | undefined {
162
+
163
+ // Find all interfaces in the file
164
+ const interfaces: ts.InterfaceDeclaration[] = [];
165
+ ts.forEachChild(sourceFile, child => {
166
+ if (ts.isInterfaceDeclaration(child)) {
167
+ interfaces.push(child);
168
+ }
169
+ });
170
+
171
+ // If this is the first interface, check for a file-level JSDoc
172
+ if (interfaces.length > 0 && interfaces[0] === statement) {
173
+ const sourceText = sourceFile.getFullText();
174
+ const firstInterfacePos = statement.getFullStart();
175
+
176
+ // Get all text before the first interface
177
+ const textBeforeInterface = sourceText.substring(0, firstInterfacePos);
178
+
179
+ // Find the last JSDoc comment before the interface
180
+ const jsDocMatches = textBeforeInterface.match(/\/\*\*([\s\S]*?)\*\//g);
181
+ if (jsDocMatches && jsDocMatches.length > 0) {
182
+ const lastJsDoc = jsDocMatches[jsDocMatches.length - 1];
183
+ const commentStartPos = textBeforeInterface.lastIndexOf(lastJsDoc);
184
+
185
+ // Create a TextRange for the comment
186
+ const textRange = TextRange.fromStringRange(
187
+ sourceText,
188
+ commentStartPos,
189
+ commentStartPos + lastJsDoc.length
190
+ );
191
+
192
+ // Parse the JSDoc comment using TSDoc
193
+ const parserContext = tsdocParser.parseRange(textRange);
194
+ const docComment = parserContext.docComment;
195
+
196
+ // Extract the parsed content
197
+ if (docComment.summarySection) {
198
+ const summary = renderDocNodes(docComment.summarySection.nodes);
199
+ if (summary) return summary;
200
+ }
201
+
202
+ // Fallback to raw comment
203
+ const comment = lastJsDoc
204
+ .replace(/^\/\*\*/, '')
205
+ .replace(/\*\/$/, '')
206
+ .split('\n')
207
+ .map(line => line.replace(/^\s*\*\s?/, ''))
208
+ .join('\n')
209
+ .trim();
210
+ return comment || undefined;
211
+ }
212
+ }
213
+
214
+ return undefined;
215
+ }
216
+
101
217
  function getHeritageType(heritage: HeritageClause): string | null {
102
218
  if (!heritage.types.length) return null;
103
219
 
@@ -217,11 +333,15 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete
217
333
  }
218
334
 
219
335
  if (type.kind === ts.SyntaxKind.LiteralType) {
220
- // Handle literal types - check if it's a null literal
336
+ // Handle literal types
221
337
  const literalType = type as ts.LiteralTypeNode;
222
338
  if (literalType.literal.kind === ts.SyntaxKind.NullKeyword) {
223
339
  return FunctionArgumentType.null;
224
340
  }
341
+ if (literalType.literal.kind === ts.SyntaxKind.StringLiteral) {
342
+ // Return the string literal value itself
343
+ return (literalType.literal as ts.StringLiteral).text;
344
+ }
225
345
  return FunctionArgumentType.any;
226
346
  }
227
347
 
@@ -411,10 +531,10 @@ function isParamsReadOnly(m: ts.PropertySignature): boolean {
411
531
  return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword);
412
532
  }
413
533
 
414
- function walkProgram(blob: IDLBlob, statement: ts.Statement, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
534
+ function walkProgram(blob: IDLBlob, statement: ts.Statement, sourceFile: ts.SourceFile, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
415
535
  switch(statement.kind) {
416
536
  case ts.SyntaxKind.InterfaceDeclaration:
417
- return processInterfaceDeclaration(statement as ts.InterfaceDeclaration, blob, definedPropertyCollector, unionTypeCollector);
537
+ return processInterfaceDeclaration(statement as ts.InterfaceDeclaration, blob, sourceFile, definedPropertyCollector, unionTypeCollector);
418
538
 
419
539
  case ts.SyntaxKind.VariableStatement:
420
540
  return processVariableStatement(statement as VariableStatement, unionTypeCollector);
@@ -444,12 +564,19 @@ function processTypeAliasDeclaration(
444
564
  function processInterfaceDeclaration(
445
565
  statement: ts.InterfaceDeclaration,
446
566
  blob: IDLBlob,
567
+ sourceFile: ts.SourceFile,
447
568
  definedPropertyCollector: DefinedPropertyCollector,
448
569
  unionTypeCollector: UnionTypeCollector
449
570
  ): ClassObject | null {
450
571
  const interfaceName = statement.name.escapedText.toString();
451
572
  const obj = new ClassObject();
452
573
 
574
+ // Capture JSDoc comment for the interface
575
+ const directComment = getJSDocComment(statement, sourceFile);
576
+ const fileComment = getFirstInterfaceJSDoc(statement, sourceFile);
577
+ obj.documentation = directComment || fileComment;
578
+
579
+
453
580
  // Process heritage clauses
454
581
  if (statement.heritageClauses) {
455
582
  const heritage = statement.heritageClauses[0];
@@ -470,7 +597,7 @@ function processInterfaceDeclaration(
470
597
 
471
598
  // Process members in batches for better performance
472
599
  const members = Array.from(statement.members);
473
- processMembersBatch(members, obj, definedPropertyCollector, unionTypeCollector);
600
+ processMembersBatch(members, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
474
601
 
475
602
  ClassObject.globalClassMap[interfaceName] = obj;
476
603
 
@@ -480,12 +607,13 @@ function processInterfaceDeclaration(
480
607
  function processMembersBatch(
481
608
  members: ts.TypeElement[],
482
609
  obj: ClassObject,
610
+ sourceFile: ts.SourceFile,
483
611
  definedPropertyCollector: DefinedPropertyCollector,
484
612
  unionTypeCollector: UnionTypeCollector
485
613
  ): void {
486
614
  for (const member of members) {
487
615
  try {
488
- processMember(member, obj, definedPropertyCollector, unionTypeCollector);
616
+ processMember(member, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
489
617
  } catch (error) {
490
618
  console.error(`Error processing member:`, error);
491
619
  }
@@ -495,24 +623,25 @@ function processMembersBatch(
495
623
  function processMember(
496
624
  member: ts.TypeElement,
497
625
  obj: ClassObject,
626
+ sourceFile: ts.SourceFile,
498
627
  definedPropertyCollector: DefinedPropertyCollector,
499
628
  unionTypeCollector: UnionTypeCollector
500
629
  ): void {
501
630
  switch(member.kind) {
502
631
  case ts.SyntaxKind.PropertySignature:
503
- processPropertySignature(member as ts.PropertySignature, obj, definedPropertyCollector, unionTypeCollector);
632
+ processPropertySignature(member as ts.PropertySignature, obj, sourceFile, definedPropertyCollector, unionTypeCollector);
504
633
  break;
505
634
 
506
635
  case ts.SyntaxKind.MethodSignature:
507
- processMethodSignature(member as ts.MethodSignature, obj, unionTypeCollector);
636
+ processMethodSignature(member as ts.MethodSignature, obj, sourceFile, unionTypeCollector);
508
637
  break;
509
638
 
510
639
  case ts.SyntaxKind.IndexSignature:
511
- processIndexSignature(member as ts.IndexSignatureDeclaration, obj, unionTypeCollector);
640
+ processIndexSignature(member as ts.IndexSignatureDeclaration, obj, sourceFile, unionTypeCollector);
512
641
  break;
513
642
 
514
643
  case ts.SyntaxKind.ConstructSignature:
515
- processConstructSignature(member as ts.ConstructSignatureDeclaration, obj, unionTypeCollector);
644
+ processConstructSignature(member as ts.ConstructSignatureDeclaration, obj, sourceFile, unionTypeCollector);
516
645
  break;
517
646
  }
518
647
  }
@@ -520,12 +649,14 @@ function processMember(
520
649
  function processPropertySignature(
521
650
  member: ts.PropertySignature,
522
651
  obj: ClassObject,
652
+ sourceFile: ts.SourceFile,
523
653
  definedPropertyCollector: DefinedPropertyCollector,
524
654
  unionTypeCollector: UnionTypeCollector
525
655
  ): void {
526
656
  const prop = new PropsDeclaration();
527
657
  prop.name = getPropName(member.name!, prop);
528
658
  prop.readonly = isParamsReadOnly(member);
659
+ prop.documentation = getJSDocComment(member, sourceFile);
529
660
 
530
661
  definedPropertyCollector.properties.add(prop.name);
531
662
 
@@ -577,10 +708,12 @@ function createAsyncProperty(prop: PropsDeclaration, mode: ParameterMode): Props
577
708
  function processMethodSignature(
578
709
  member: ts.MethodSignature,
579
710
  obj: ClassObject,
711
+ sourceFile: ts.SourceFile,
580
712
  unionTypeCollector: UnionTypeCollector
581
713
  ): void {
582
714
  const f = new FunctionDeclaration();
583
715
  f.name = getPropName(member.name!);
716
+ f.documentation = getJSDocComment(member, sourceFile);
584
717
  f.args = member.parameters.map(params =>
585
718
  paramsNodeToArguments(params, unionTypeCollector)
586
719
  );
@@ -639,6 +772,7 @@ function createAsyncMethod(
639
772
  function processIndexSignature(
640
773
  member: ts.IndexSignatureDeclaration,
641
774
  obj: ClassObject,
775
+ sourceFile: ts.SourceFile,
642
776
  unionTypeCollector: UnionTypeCollector
643
777
  ): void {
644
778
  const prop = new IndexedPropertyDeclaration();
@@ -659,6 +793,7 @@ function processIndexSignature(
659
793
  function processConstructSignature(
660
794
  member: ts.ConstructSignatureDeclaration,
661
795
  obj: ClassObject,
796
+ sourceFile: ts.SourceFile,
662
797
  unionTypeCollector: UnionTypeCollector
663
798
  ): void {
664
799
  const c = new FunctionDeclaration();
@@ -45,6 +45,7 @@ export class PropsDeclaration {
45
45
  isSymbol?: boolean;
46
46
  readonly: boolean;
47
47
  optional: boolean;
48
+ documentation?: string;
48
49
  }
49
50
 
50
51
  export class IndexedPropertyDeclaration extends PropsDeclaration {
@@ -77,6 +78,7 @@ export class ClassObject {
77
78
  staticMethods: FunctionDeclaration[] = [];
78
79
  construct?: FunctionDeclaration;
79
80
  kind: ClassObjectKind = ClassObjectKind.interface;
81
+ documentation?: string;
80
82
  }
81
83
 
82
84
  export class FunctionObject {