@openwebf/webf 0.22.5 → 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 +117 -17
- package/dist/commands.js +25 -6
- package/dist/react.js +47 -2
- package/package.json +3 -1
- package/src/analyzer.ts +145 -10
- package/src/commands.ts +29 -5
- package/src/declaration.ts +2 -0
- package/src/react.ts +53 -4
- package/templates/react.component.tsx.tpl +57 -6
- package/test/union-types-jsdoc.test.ts +168 -0
- package/dist/analyzer_original.js +0 -467
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
|
|
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();
|
package/src/commands.ts
CHANGED
|
@@ -395,6 +395,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
395
395
|
// Determine if we need to create a new project
|
|
396
396
|
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
397
397
|
|
|
398
|
+
// Track if this is an existing project (has all required files)
|
|
399
|
+
const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
400
|
+
|
|
398
401
|
let framework = options.framework;
|
|
399
402
|
let packageName = options.packageName;
|
|
400
403
|
|
|
@@ -620,7 +623,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
620
623
|
// Handle npm publishing if requested via command line option
|
|
621
624
|
if (options.publishToNpm && framework) {
|
|
622
625
|
try {
|
|
623
|
-
await buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
|
|
626
|
+
await buildAndPublishPackage(resolvedDistPath, options.npmRegistry, isExistingProject);
|
|
624
627
|
} catch (error) {
|
|
625
628
|
console.error('\nError during npm publish:', error);
|
|
626
629
|
process.exit(1);
|
|
@@ -655,7 +658,8 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
655
658
|
try {
|
|
656
659
|
await buildAndPublishPackage(
|
|
657
660
|
resolvedDistPath,
|
|
658
|
-
registryAnswer.registry || undefined
|
|
661
|
+
registryAnswer.registry || undefined,
|
|
662
|
+
isExistingProject
|
|
659
663
|
);
|
|
660
664
|
} catch (error) {
|
|
661
665
|
console.error('\nError during npm publish:', error);
|
|
@@ -760,20 +764,40 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
760
764
|
}
|
|
761
765
|
}
|
|
762
766
|
|
|
763
|
-
async function buildAndPublishPackage(packagePath: string, registry?: string): Promise<void> {
|
|
767
|
+
async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
|
|
764
768
|
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
765
769
|
|
|
766
770
|
if (!fs.existsSync(packageJsonPath)) {
|
|
767
771
|
throw new Error(`No package.json found in ${packagePath}`);
|
|
768
772
|
}
|
|
769
773
|
|
|
770
|
-
|
|
774
|
+
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
771
775
|
const packageName = packageJson.name;
|
|
772
|
-
|
|
776
|
+
let packageVersion = packageJson.version;
|
|
773
777
|
|
|
774
778
|
// First, ensure dependencies are installed and build the package
|
|
775
779
|
await buildPackage(packagePath);
|
|
776
780
|
|
|
781
|
+
// If this is an existing project, increment the patch version before publishing
|
|
782
|
+
if (isExistingProject) {
|
|
783
|
+
console.log(`\nIncrementing version for existing project...`);
|
|
784
|
+
const versionResult = spawnSync(NPM, ['version', 'patch', '--no-git-tag-version'], {
|
|
785
|
+
cwd: packagePath,
|
|
786
|
+
encoding: 'utf-8',
|
|
787
|
+
stdio: 'pipe'
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
if (versionResult.status !== 0) {
|
|
791
|
+
console.error('Failed to increment version:', versionResult.stderr);
|
|
792
|
+
throw new Error('Failed to increment version');
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Re-read package.json to get the new version
|
|
796
|
+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
797
|
+
packageVersion = packageJson.version;
|
|
798
|
+
console.log(`Version updated to ${packageVersion}`);
|
|
799
|
+
}
|
|
800
|
+
|
|
777
801
|
// Set registry if provided
|
|
778
802
|
if (registry) {
|
|
779
803
|
console.log(`\nUsing npm registry: ${registry}`);
|
package/src/declaration.ts
CHANGED
|
@@ -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 {
|
package/src/react.ts
CHANGED
|
@@ -4,13 +4,16 @@ import path from 'path';
|
|
|
4
4
|
import {ParameterType} from "./analyzer";
|
|
5
5
|
import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject} from "./declaration";
|
|
6
6
|
import {IDLBlob} from "./IDLBlob";
|
|
7
|
-
import {getPointerType, isPointerType} from "./utils";
|
|
7
|
+
import {getPointerType, isPointerType, isUnionType} from "./utils";
|
|
8
8
|
|
|
9
9
|
function readTemplate(name: string) {
|
|
10
10
|
return fs.readFileSync(path.join(__dirname, '../templates/' + name + '.tpl'), {encoding: 'utf-8'});
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function generateReturnType(type: ParameterType) {
|
|
14
|
+
if (isUnionType(type)) {
|
|
15
|
+
return (type.value as ParameterType[]).map(v => `'${v.value}'`).join(' | ');
|
|
16
|
+
}
|
|
14
17
|
if (isPointerType(type)) {
|
|
15
18
|
const pointerType = getPointerType(type);
|
|
16
19
|
return pointerType;
|
|
@@ -79,6 +82,20 @@ function generateMethodDeclaration(method: FunctionDeclaration) {
|
|
|
79
82
|
return `${methodName}(${args}): ${returnType};`;
|
|
80
83
|
}
|
|
81
84
|
|
|
85
|
+
function generateMethodDeclarationWithDocs(method: FunctionDeclaration, indent: string = ''): string {
|
|
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
|
+
}
|
|
98
|
+
|
|
82
99
|
function toReactEventName(name: string) {
|
|
83
100
|
const eventName = 'on-' + name;
|
|
84
101
|
return _.camelCase(eventName);
|
|
@@ -115,10 +132,14 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
|
|
|
115
132
|
const events = classObjects.filter(object => {
|
|
116
133
|
return object.name.endsWith('Events');
|
|
117
134
|
});
|
|
135
|
+
const methods = classObjects.filter(object => {
|
|
136
|
+
return object.name.endsWith('Methods');
|
|
137
|
+
});
|
|
118
138
|
|
|
119
139
|
const others = classObjects.filter(object => {
|
|
120
140
|
return !object.name.endsWith('Properties')
|
|
121
|
-
&& !object.name.endsWith('Events')
|
|
141
|
+
&& !object.name.endsWith('Events')
|
|
142
|
+
&& !object.name.endsWith('Methods');
|
|
122
143
|
});
|
|
123
144
|
|
|
124
145
|
// Include type aliases
|
|
@@ -128,6 +149,21 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
|
|
|
128
149
|
|
|
129
150
|
const dependencies = [
|
|
130
151
|
typeAliasDeclarations,
|
|
152
|
+
// Include Methods interfaces as dependencies
|
|
153
|
+
methods.map(object => {
|
|
154
|
+
const methodDeclarations = object.methods.map(method => {
|
|
155
|
+
return generateMethodDeclarationWithDocs(method, ' ');
|
|
156
|
+
}).join('\n');
|
|
157
|
+
|
|
158
|
+
let interfaceDoc = '';
|
|
159
|
+
if (object.documentation) {
|
|
160
|
+
interfaceDoc = `/**\n${object.documentation.split('\n').map(line => ` * ${line}`).join('\n')}\n */\n`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return `${interfaceDoc}interface ${object.name} {
|
|
164
|
+
${methodDeclarations}
|
|
165
|
+
}`;
|
|
166
|
+
}).join('\n\n'),
|
|
131
167
|
others.map(object => {
|
|
132
168
|
const props = object.props.map(prop => {
|
|
133
169
|
if (prop.optional) {
|
|
@@ -146,8 +182,8 @@ interface ${object.name} {
|
|
|
146
182
|
// Generate all components from this file
|
|
147
183
|
const components: string[] = [];
|
|
148
184
|
|
|
149
|
-
// Create a map of component names to their properties and
|
|
150
|
-
const componentMap = new Map<string, { properties?: ClassObject, events?: ClassObject }>();
|
|
185
|
+
// Create a map of component names to their properties, events, and methods
|
|
186
|
+
const componentMap = new Map<string, { properties?: ClassObject, events?: ClassObject, methods?: ClassObject }>();
|
|
151
187
|
|
|
152
188
|
// Process all Properties interfaces
|
|
153
189
|
properties.forEach(prop => {
|
|
@@ -167,6 +203,15 @@ interface ${object.name} {
|
|
|
167
203
|
componentMap.get(componentName)!.events = event;
|
|
168
204
|
});
|
|
169
205
|
|
|
206
|
+
// Process all Methods interfaces
|
|
207
|
+
methods.forEach(method => {
|
|
208
|
+
const componentName = method.name.replace(/Methods$/, '');
|
|
209
|
+
if (!componentMap.has(componentName)) {
|
|
210
|
+
componentMap.set(componentName, {});
|
|
211
|
+
}
|
|
212
|
+
componentMap.get(componentName)!.methods = method;
|
|
213
|
+
});
|
|
214
|
+
|
|
170
215
|
// If we have multiple components, we need to generate a combined file
|
|
171
216
|
const componentEntries = Array.from(componentMap.entries());
|
|
172
217
|
|
|
@@ -202,6 +247,7 @@ interface ${object.name} {
|
|
|
202
247
|
className: className,
|
|
203
248
|
properties: component.properties,
|
|
204
249
|
events: component.events,
|
|
250
|
+
methods: component.methods,
|
|
205
251
|
classObjectDictionary,
|
|
206
252
|
dependencies,
|
|
207
253
|
blob,
|
|
@@ -209,6 +255,7 @@ interface ${object.name} {
|
|
|
209
255
|
toWebFTagName,
|
|
210
256
|
generateReturnType,
|
|
211
257
|
generateMethodDeclaration,
|
|
258
|
+
generateMethodDeclarationWithDocs,
|
|
212
259
|
generateEventHandlerType,
|
|
213
260
|
getEventType,
|
|
214
261
|
});
|
|
@@ -240,6 +287,7 @@ interface ${object.name} {
|
|
|
240
287
|
className: className,
|
|
241
288
|
properties: component.properties,
|
|
242
289
|
events: component.events,
|
|
290
|
+
methods: component.methods,
|
|
243
291
|
classObjectDictionary,
|
|
244
292
|
dependencies: '', // Dependencies will be at the top
|
|
245
293
|
blob,
|
|
@@ -247,6 +295,7 @@ interface ${object.name} {
|
|
|
247
295
|
toWebFTagName,
|
|
248
296
|
generateReturnType,
|
|
249
297
|
generateMethodDeclaration,
|
|
298
|
+
generateMethodDeclarationWithDocs,
|
|
250
299
|
generateEventHandlerType,
|
|
251
300
|
getEventType,
|
|
252
301
|
});
|
|
@@ -7,12 +7,18 @@ export interface <%= className %>Props {
|
|
|
7
7
|
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
8
8
|
<% var propName = _.camelCase(prop.name); %>
|
|
9
9
|
<% var attributeName = _.kebabCase(prop.name); %>
|
|
10
|
+
<% if (prop.documentation) { %>
|
|
11
|
+
/**
|
|
12
|
+
* <%= prop.documentation.split('\n').join('\n * ') %>
|
|
13
|
+
*/
|
|
14
|
+
<% } else { %>
|
|
10
15
|
/**
|
|
11
16
|
* <%= propName %> property
|
|
12
17
|
<% if (prop.optional) { %>
|
|
13
18
|
* @default undefined
|
|
14
19
|
<% } %>
|
|
15
20
|
*/
|
|
21
|
+
<% } %>
|
|
16
22
|
<% if (prop.optional) { %>
|
|
17
23
|
<%= propName %>?: <%= generateReturnType(prop.type) %>;
|
|
18
24
|
<% } else { %>
|
|
@@ -22,9 +28,15 @@ export interface <%= className %>Props {
|
|
|
22
28
|
<% }); %>
|
|
23
29
|
<% _.forEach(events?.props, function(prop, index) { %>
|
|
24
30
|
<% var propName = toReactEventName(prop.name); %>
|
|
31
|
+
<% if (prop.documentation) { %>
|
|
32
|
+
/**
|
|
33
|
+
* <%= prop.documentation.split('\n').join('\n * ') %>
|
|
34
|
+
*/
|
|
35
|
+
<% } else { %>
|
|
25
36
|
/**
|
|
26
37
|
* <%= prop.name %> event handler
|
|
27
38
|
*/
|
|
39
|
+
<% } %>
|
|
28
40
|
<%= propName %>?: (event: <%= getEventType(prop.type) %>) => void;
|
|
29
41
|
|
|
30
42
|
<% }); %>
|
|
@@ -44,24 +56,63 @@ export interface <%= className %>Props {
|
|
|
44
56
|
className?: string;
|
|
45
57
|
}
|
|
46
58
|
|
|
59
|
+
<% if (methods && methods.methods.length > 0) { %>
|
|
60
|
+
/**
|
|
61
|
+
* Element interface with methods accessible via ref
|
|
62
|
+
* @example
|
|
63
|
+
* ```tsx
|
|
64
|
+
* const ref = useRef<<%= className %>Element>(null);
|
|
65
|
+
* // Call methods on the element
|
|
66
|
+
* ref.current?.finishRefresh('success');
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
<% } %>
|
|
47
70
|
export interface <%= className %>Element extends WebFElementWithMethods<{
|
|
48
|
-
<% _.forEach(
|
|
49
|
-
|
|
71
|
+
<% _.forEach(methods?.methods, function(method, index) { %>
|
|
72
|
+
<%= generateMethodDeclarationWithDocs(method, ' ') %>
|
|
50
73
|
<% }); %>
|
|
51
74
|
}> {}
|
|
52
75
|
|
|
76
|
+
<% if (properties?.documentation || methods?.documentation || events?.documentation) { %>
|
|
77
|
+
<% const docs = properties?.documentation || methods?.documentation || events?.documentation; %>
|
|
78
|
+
/**
|
|
79
|
+
* <%= docs %>
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```tsx<% if (methods && methods.methods.length > 0) { %>
|
|
83
|
+
* const ref = useRef<<%= className %>Element>(null);<% } %>
|
|
84
|
+
*
|
|
85
|
+
* <<%= className %><% if (methods && methods.methods.length > 0) { %>
|
|
86
|
+
* ref={ref}<% } %>
|
|
87
|
+
* // Add props here
|
|
88
|
+
* >
|
|
89
|
+
* Content
|
|
90
|
+
* </<%= className %>><% if (methods && methods.methods.length > 0) { %>
|
|
91
|
+
*
|
|
92
|
+
* // Call methods on the element
|
|
93
|
+
* ref.current?.finishRefresh('success');<% } %>
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
<% } else { %>
|
|
53
97
|
/**
|
|
54
98
|
* <%= className %> - WebF <%= className %> component
|
|
55
99
|
*
|
|
56
100
|
* @example
|
|
57
|
-
* ```tsx
|
|
58
|
-
* <<%= className %>
|
|
59
|
-
*
|
|
101
|
+
* ```tsx<% if (methods && methods.methods.length > 0) { %>
|
|
102
|
+
* const ref = useRef<<%= className %>Element>(null);<% } %>
|
|
103
|
+
*
|
|
104
|
+
* <<%= className %><% if (methods && methods.methods.length > 0) { %>
|
|
105
|
+
* ref={ref}<% } %>
|
|
106
|
+
* // Add props here
|
|
60
107
|
* >
|
|
61
108
|
* Content
|
|
62
|
-
* </<%= className
|
|
109
|
+
* </<%= className %>><% if (methods && methods.methods.length > 0) { %>
|
|
110
|
+
*
|
|
111
|
+
* // Call methods on the element
|
|
112
|
+
* ref.current?.finishRefresh('success');<% } %>
|
|
63
113
|
* ```
|
|
64
114
|
*/
|
|
115
|
+
<% } %>
|
|
65
116
|
export const <%= className %> = createWebFComponent<<%= className %>Element, <%= className %>Props>({
|
|
66
117
|
tagName: '<%= toWebFTagName(className) %>',
|
|
67
118
|
displayName: '<%= className %>',
|