@openwebf/webf 0.22.9 → 0.22.11

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
@@ -66,11 +66,17 @@ const TYPE_REFERENCE_MAP = {
66
66
  };
67
67
  function analyzer(blob, definedPropertyCollector, unionTypeCollector) {
68
68
  try {
69
- // Check cache first
70
- let sourceFile = sourceFileCache.get(blob.source);
71
- if (!sourceFile) {
69
+ // Check cache first - consider both file path and content
70
+ const cacheEntry = sourceFileCache.get(blob.source);
71
+ let sourceFile;
72
+ if (cacheEntry && cacheEntry.content === blob.raw) {
73
+ // Cache hit with same content
74
+ sourceFile = cacheEntry.sourceFile;
75
+ }
76
+ else {
77
+ // Cache miss or content changed - parse and update cache
72
78
  sourceFile = typescript_1.default.createSourceFile(blob.source, blob.raw, typescript_1.ScriptTarget.ES2020);
73
- sourceFileCache.set(blob.source, sourceFile);
79
+ sourceFileCache.set(blob.source, { content: blob.raw, sourceFile });
74
80
  }
75
81
  blob.objects = sourceFile.statements
76
82
  .map(statement => {
@@ -292,6 +298,8 @@ function getParameterBaseType(type, mode) {
292
298
  if (mode)
293
299
  mode.staticMethod = true;
294
300
  return handleGenericWrapper(typeReference, mode);
301
+ case 'CustomEvent':
302
+ return handleCustomEventType(typeReference);
295
303
  default:
296
304
  if (identifier.includes('SupportAsync')) {
297
305
  return handleSupportAsyncType(identifier, typeReference, mode);
@@ -344,6 +352,92 @@ function handleGenericWrapper(typeReference, mode) {
344
352
  const argument = typeReference.typeArguments[0];
345
353
  return getParameterBaseType(argument, mode);
346
354
  }
355
+ function handleCustomEventType(typeReference) {
356
+ // Handle CustomEvent<T> by returning the full type with generic parameter
357
+ if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
358
+ return 'CustomEvent';
359
+ }
360
+ const argument = typeReference.typeArguments[0];
361
+ let genericType;
362
+ if (typescript_1.default.isTypeReferenceNode(argument) && typescript_1.default.isIdentifier(argument.typeName)) {
363
+ const typeName = argument.typeName.text;
364
+ // Check if it's a mapped type reference like 'int' or 'double'
365
+ const mappedType = TYPE_REFERENCE_MAP[typeName];
366
+ if (mappedType !== undefined) {
367
+ switch (mappedType) {
368
+ case declaration_1.FunctionArgumentType.boolean:
369
+ genericType = 'boolean';
370
+ break;
371
+ case declaration_1.FunctionArgumentType.dom_string:
372
+ genericType = 'string';
373
+ break;
374
+ case declaration_1.FunctionArgumentType.double:
375
+ case declaration_1.FunctionArgumentType.int:
376
+ genericType = 'number';
377
+ break;
378
+ case declaration_1.FunctionArgumentType.any:
379
+ genericType = 'any';
380
+ break;
381
+ case declaration_1.FunctionArgumentType.void:
382
+ genericType = 'void';
383
+ break;
384
+ case declaration_1.FunctionArgumentType.function:
385
+ genericType = 'Function';
386
+ break;
387
+ case declaration_1.FunctionArgumentType.promise:
388
+ genericType = 'Promise<any>';
389
+ break;
390
+ default:
391
+ genericType = typeName;
392
+ }
393
+ }
394
+ else {
395
+ // For other type references, use the type name directly
396
+ genericType = typeName;
397
+ }
398
+ }
399
+ else if (typescript_1.default.isLiteralTypeNode(argument) && typescript_1.default.isStringLiteral(argument.literal)) {
400
+ genericType = argument.literal.text;
401
+ }
402
+ else {
403
+ // Handle basic types (boolean, string, number, etc.)
404
+ const basicType = BASIC_TYPE_MAP[argument.kind];
405
+ if (basicType !== undefined) {
406
+ switch (basicType) {
407
+ case declaration_1.FunctionArgumentType.boolean:
408
+ genericType = 'boolean';
409
+ break;
410
+ case declaration_1.FunctionArgumentType.dom_string:
411
+ genericType = 'string';
412
+ break;
413
+ case declaration_1.FunctionArgumentType.double:
414
+ case declaration_1.FunctionArgumentType.int:
415
+ genericType = 'number';
416
+ break;
417
+ case declaration_1.FunctionArgumentType.any:
418
+ genericType = 'any';
419
+ break;
420
+ case declaration_1.FunctionArgumentType.void:
421
+ genericType = 'void';
422
+ break;
423
+ case declaration_1.FunctionArgumentType.null:
424
+ genericType = 'null';
425
+ break;
426
+ case declaration_1.FunctionArgumentType.undefined:
427
+ genericType = 'undefined';
428
+ break;
429
+ default:
430
+ genericType = 'any';
431
+ }
432
+ }
433
+ else {
434
+ // For truly complex types, fallback to 'any' to avoid errors
435
+ console.warn('Complex generic type in CustomEvent, using any');
436
+ genericType = 'any';
437
+ }
438
+ }
439
+ return `CustomEvent<${genericType}>`;
440
+ }
347
441
  function handleSupportAsyncType(identifier, typeReference, mode) {
348
442
  if (mode) {
349
443
  mode.supportAsync = true;
package/dist/generator.js CHANGED
@@ -287,64 +287,12 @@ function reactGen(_a) {
287
287
  (0, logger_1.error)(`Error generating React component for ${blob.filename}`, err);
288
288
  }
289
289
  }));
290
- // Generate or update index file
290
+ // Generate index file (always regenerate to avoid duplicates)
291
291
  const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
292
292
  const newExports = (0, react_1.generateReactIndex)(blobs);
293
- if (fs_1.default.existsSync(indexFilePath)) {
294
- // Read existing index file
295
- const existingContent = fs_1.default.readFileSync(indexFilePath, 'utf-8');
296
- const existingLines = existingContent.split('\n');
297
- // Parse new exports
298
- const newExportLines = newExports.split('\n').filter(line => line.startsWith('export '));
299
- // Find which exports are missing
300
- const missingExports = [];
301
- for (const newExport of newExportLines) {
302
- // Extract the export statement to check if it exists
303
- const exportMatch = newExport.match(/export\s*{\s*([^}]+)\s*}\s*from\s*["']([^"']+)["']/);
304
- if (exportMatch) {
305
- const [, exportNames, modulePath] = exportMatch;
306
- const exportedItems = exportNames.split(',').map(s => s.trim());
307
- // Check if this exact export exists
308
- const exists = existingLines.some(line => {
309
- if (!line.startsWith('export '))
310
- return false;
311
- const lineMatch = line.match(/export\s*{\s*([^}]+)\s*}\s*from\s*["']([^"']+)["']/);
312
- if (!lineMatch)
313
- return false;
314
- const [, lineExportNames, lineModulePath] = lineMatch;
315
- const lineExportedItems = lineExportNames.split(',').map(s => s.trim());
316
- // Check if same module and same exports
317
- return lineModulePath === modulePath &&
318
- exportedItems.every(item => lineExportedItems.includes(item)) &&
319
- lineExportedItems.every(item => exportedItems.includes(item));
320
- });
321
- if (!exists) {
322
- missingExports.push(newExport);
323
- }
324
- }
325
- }
326
- // If there are missing exports, append them
327
- if (missingExports.length > 0) {
328
- let updatedContent = existingContent.trimRight();
329
- if (!updatedContent.endsWith('\n')) {
330
- updatedContent += '\n';
331
- }
332
- updatedContent += missingExports.join('\n') + '\n';
333
- if (writeFileIfChanged(indexFilePath, updatedContent)) {
334
- filesChanged++;
335
- (0, logger_1.debug)(`Updated: index.ts (added ${missingExports.length} exports)`);
336
- }
337
- }
338
- else {
339
- (0, logger_1.debug)(`Skipped: index.ts (all exports already exist)`);
340
- }
341
- }
342
- else {
343
- // File doesn't exist, create it
344
- if (writeFileIfChanged(indexFilePath, newExports)) {
345
- filesChanged++;
346
- (0, logger_1.debug)(`Generated: index.ts`);
347
- }
293
+ if (writeFileIfChanged(indexFilePath, newExports)) {
294
+ filesChanged++;
295
+ (0, logger_1.debug)(`Generated: index.ts`);
348
296
  }
349
297
  (0, logger_1.timeEnd)('reactGen');
350
298
  (0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
package/dist/react.js CHANGED
@@ -63,6 +63,10 @@ function getEventType(type) {
63
63
  return 'Event';
64
64
  }
65
65
  const pointerType = (0, utils_1.getPointerType)(type);
66
+ // Handle CustomEvent with generic parameter
67
+ if (pointerType.startsWith('CustomEvent<') && pointerType.endsWith('>')) {
68
+ return pointerType;
69
+ }
66
70
  if (pointerType === 'CustomEvent') {
67
71
  return 'CustomEvent';
68
72
  }
@@ -319,8 +323,15 @@ function generateReactIndex(blobs) {
319
323
  }).filter(component => {
320
324
  return component.className.length > 0;
321
325
  });
326
+ // Deduplicate components by className, keeping the first occurrence
327
+ const deduplicatedComponents = new Map();
328
+ components.forEach(component => {
329
+ if (!deduplicatedComponents.has(component.className)) {
330
+ deduplicatedComponents.set(component.className, component);
331
+ }
332
+ });
322
333
  const content = lodash_1.default.template(readTemplate('react.index.ts'))({
323
- components,
334
+ components: Array.from(deduplicatedComponents.values()),
324
335
  });
325
336
  return content.split('\n').filter(str => {
326
337
  return str.trim().length > 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwebf/webf",
3
- "version": "0.22.9",
3
+ "version": "0.22.11",
4
4
  "description": "Command line tools for WebF",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/analyzer.ts CHANGED
@@ -26,7 +26,7 @@ export interface UnionTypeCollector {
26
26
  }
27
27
 
28
28
  // Cache for parsed source files to avoid re-parsing
29
- const sourceFileCache = new Map<string, ts.SourceFile>();
29
+ const sourceFileCache = new Map<string, { content: string; sourceFile: ts.SourceFile }>();
30
30
 
31
31
  // Cache for type conversions to avoid redundant processing
32
32
  const typeConversionCache = new Map<string, ParameterType>();
@@ -56,11 +56,17 @@ const TYPE_REFERENCE_MAP: Record<string, FunctionArgumentType> = {
56
56
 
57
57
  export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropertyCollector, unionTypeCollector: UnionTypeCollector) {
58
58
  try {
59
- // Check cache first
60
- let sourceFile = sourceFileCache.get(blob.source);
61
- if (!sourceFile) {
59
+ // Check cache first - consider both file path and content
60
+ const cacheEntry = sourceFileCache.get(blob.source);
61
+ let sourceFile: ts.SourceFile;
62
+
63
+ if (cacheEntry && cacheEntry.content === blob.raw) {
64
+ // Cache hit with same content
65
+ sourceFile = cacheEntry.sourceFile;
66
+ } else {
67
+ // Cache miss or content changed - parse and update cache
62
68
  sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020);
63
- sourceFileCache.set(blob.source, sourceFile);
69
+ sourceFileCache.set(blob.source, { content: blob.raw, sourceFile });
64
70
  }
65
71
 
66
72
  blob.objects = sourceFile.statements
@@ -324,6 +330,9 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete
324
330
  if (mode) mode.staticMethod = true;
325
331
  return handleGenericWrapper(typeReference, mode);
326
332
 
333
+ case 'CustomEvent':
334
+ return handleCustomEventType(typeReference);
335
+
327
336
  default:
328
337
  if (identifier.includes('SupportAsync')) {
329
338
  return handleSupportAsyncType(identifier, typeReference, mode);
@@ -386,6 +395,93 @@ function handleGenericWrapper(typeReference: ts.TypeReferenceNode, mode?: Parame
386
395
  return getParameterBaseType(argument, mode);
387
396
  }
388
397
 
398
+ function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBaseType {
399
+ // Handle CustomEvent<T> by returning the full type with generic parameter
400
+ if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
401
+ return 'CustomEvent';
402
+ }
403
+
404
+ const argument = typeReference.typeArguments[0];
405
+ let genericType: string;
406
+
407
+ if (ts.isTypeReferenceNode(argument) && ts.isIdentifier(argument.typeName)) {
408
+ const typeName = argument.typeName.text;
409
+
410
+ // Check if it's a mapped type reference like 'int' or 'double'
411
+ const mappedType = TYPE_REFERENCE_MAP[typeName];
412
+ if (mappedType !== undefined) {
413
+ switch (mappedType) {
414
+ case FunctionArgumentType.boolean:
415
+ genericType = 'boolean';
416
+ break;
417
+ case FunctionArgumentType.dom_string:
418
+ genericType = 'string';
419
+ break;
420
+ case FunctionArgumentType.double:
421
+ case FunctionArgumentType.int:
422
+ genericType = 'number';
423
+ break;
424
+ case FunctionArgumentType.any:
425
+ genericType = 'any';
426
+ break;
427
+ case FunctionArgumentType.void:
428
+ genericType = 'void';
429
+ break;
430
+ case FunctionArgumentType.function:
431
+ genericType = 'Function';
432
+ break;
433
+ case FunctionArgumentType.promise:
434
+ genericType = 'Promise<any>';
435
+ break;
436
+ default:
437
+ genericType = typeName;
438
+ }
439
+ } else {
440
+ // For other type references, use the type name directly
441
+ genericType = typeName;
442
+ }
443
+ } else if (ts.isLiteralTypeNode(argument) && ts.isStringLiteral(argument.literal)) {
444
+ genericType = argument.literal.text;
445
+ } else {
446
+ // Handle basic types (boolean, string, number, etc.)
447
+ const basicType = BASIC_TYPE_MAP[argument.kind];
448
+ if (basicType !== undefined) {
449
+ switch (basicType) {
450
+ case FunctionArgumentType.boolean:
451
+ genericType = 'boolean';
452
+ break;
453
+ case FunctionArgumentType.dom_string:
454
+ genericType = 'string';
455
+ break;
456
+ case FunctionArgumentType.double:
457
+ case FunctionArgumentType.int:
458
+ genericType = 'number';
459
+ break;
460
+ case FunctionArgumentType.any:
461
+ genericType = 'any';
462
+ break;
463
+ case FunctionArgumentType.void:
464
+ genericType = 'void';
465
+ break;
466
+ case FunctionArgumentType.null:
467
+ genericType = 'null';
468
+ break;
469
+ case FunctionArgumentType.undefined:
470
+ genericType = 'undefined';
471
+ break;
472
+ default:
473
+ genericType = 'any';
474
+ }
475
+ } else {
476
+ // For truly complex types, fallback to 'any' to avoid errors
477
+ console.warn('Complex generic type in CustomEvent, using any');
478
+ genericType = 'any';
479
+ }
480
+ }
481
+
482
+ return `CustomEvent<${genericType}>`;
483
+ }
484
+
389
485
  function handleSupportAsyncType(identifier: string, typeReference: ts.TypeReferenceNode, mode?: ParameterMode): ParameterBaseType {
390
486
  if (mode) {
391
487
  mode.supportAsync = true;
package/src/generator.ts CHANGED
@@ -321,68 +321,13 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
321
321
  }
322
322
  });
323
323
 
324
- // Generate or update index file
324
+ // Generate index file (always regenerate to avoid duplicates)
325
325
  const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
326
326
  const newExports = generateReactIndex(blobs);
327
327
 
328
- if (fs.existsSync(indexFilePath)) {
329
- // Read existing index file
330
- const existingContent = fs.readFileSync(indexFilePath, 'utf-8');
331
- const existingLines = existingContent.split('\n');
332
-
333
- // Parse new exports
334
- const newExportLines = newExports.split('\n').filter(line => line.startsWith('export '));
335
-
336
- // Find which exports are missing
337
- const missingExports: string[] = [];
338
- for (const newExport of newExportLines) {
339
- // Extract the export statement to check if it exists
340
- const exportMatch = newExport.match(/export\s*{\s*([^}]+)\s*}\s*from\s*["']([^"']+)["']/);
341
- if (exportMatch) {
342
- const [, exportNames, modulePath] = exportMatch;
343
- const exportedItems = exportNames.split(',').map(s => s.trim());
344
-
345
- // Check if this exact export exists
346
- const exists = existingLines.some(line => {
347
- if (!line.startsWith('export ')) return false;
348
- const lineMatch = line.match(/export\s*{\s*([^}]+)\s*}\s*from\s*["']([^"']+)["']/);
349
- if (!lineMatch) return false;
350
- const [, lineExportNames, lineModulePath] = lineMatch;
351
- const lineExportedItems = lineExportNames.split(',').map(s => s.trim());
352
-
353
- // Check if same module and same exports
354
- return lineModulePath === modulePath &&
355
- exportedItems.every(item => lineExportedItems.includes(item)) &&
356
- lineExportedItems.every(item => exportedItems.includes(item));
357
- });
358
-
359
- if (!exists) {
360
- missingExports.push(newExport);
361
- }
362
- }
363
- }
364
-
365
- // If there are missing exports, append them
366
- if (missingExports.length > 0) {
367
- let updatedContent = existingContent.trimRight();
368
- if (!updatedContent.endsWith('\n')) {
369
- updatedContent += '\n';
370
- }
371
- updatedContent += missingExports.join('\n') + '\n';
372
-
373
- if (writeFileIfChanged(indexFilePath, updatedContent)) {
374
- filesChanged++;
375
- debug(`Updated: index.ts (added ${missingExports.length} exports)`);
376
- }
377
- } else {
378
- debug(`Skipped: index.ts (all exports already exist)`);
379
- }
380
- } else {
381
- // File doesn't exist, create it
382
- if (writeFileIfChanged(indexFilePath, newExports)) {
383
- filesChanged++;
384
- debug(`Generated: index.ts`);
385
- }
328
+ if (writeFileIfChanged(indexFilePath, newExports)) {
329
+ filesChanged++;
330
+ debug(`Generated: index.ts`);
386
331
  }
387
332
 
388
333
  timeEnd('reactGen');
package/src/react.ts CHANGED
@@ -61,6 +61,12 @@ function getEventType(type: ParameterType) {
61
61
  return 'Event';
62
62
  }
63
63
  const pointerType = getPointerType(type);
64
+
65
+ // Handle CustomEvent with generic parameter
66
+ if (pointerType.startsWith('CustomEvent<') && pointerType.endsWith('>')) {
67
+ return pointerType;
68
+ }
69
+
64
70
  if (pointerType === 'CustomEvent') {
65
71
  return 'CustomEvent';
66
72
  }
@@ -360,8 +366,16 @@ export function generateReactIndex(blobs: IDLBlob[]) {
360
366
  return component.className.length > 0;
361
367
  });
362
368
 
369
+ // Deduplicate components by className, keeping the first occurrence
370
+ const deduplicatedComponents = new Map<string, { className: string; fileName: string; relativeDir: string }>();
371
+ components.forEach(component => {
372
+ if (!deduplicatedComponents.has(component.className)) {
373
+ deduplicatedComponents.set(component.className, component);
374
+ }
375
+ });
376
+
363
377
  const content = _.template(readTemplate('react.index.ts'))({
364
- components,
378
+ components: Array.from(deduplicatedComponents.values()),
365
379
  });
366
380
 
367
381
  return content.split('\n').filter(str => {