typenative 0.1.2 → 0.1.3

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/bin/transpiler.js CHANGED
@@ -338,6 +338,8 @@ const interfacePropertyTypes = new Map();
338
338
  const typeAliases = new Map();
339
339
  const enumNames = new Set();
340
340
  const enumBaseTypes = new Map();
341
+ // Member types of heterogeneous enums ("E.A" → float64 / string)
342
+ const enumMemberGoTypes = new Map();
341
343
  // Maps local TS name → Go qualified name (e.g. 'Println' → 'fmt.Println', 'myFmt' → 'fmt')
342
344
  const importAliases = new Map();
343
345
  // Tracks static methods per class: Set of "ClassName.methodName" strings
@@ -390,15 +392,19 @@ const OPERATOR_TEXT = {
390
392
  function operatorTokenText(token) {
391
393
  return OPERATOR_TEXT[token?.kind] ?? token?.kind ?? '';
392
394
  }
393
- function defaultParseFunction(_code) {
395
+ function defaultParseFunction(_code, _tsx) {
394
396
  throw new Error('transpileToNative: no parse function injected (options.parse)');
395
397
  }
396
398
  // Parses via the injected parser and links each node to its parent: the JSON
397
399
  // AST carries no parent pointers, but the transpiler walks up via node.parent.
398
400
  function parseSource(code) {
399
- const sourceFile = parseFunction(code);
401
+ const sourceFile = parseFunction(code, currentFileIsTsx);
400
402
  stripThisParameters(sourceFile);
401
403
  lowerIndexSignatures(sourceFile);
404
+ lowerAmbientDeclarations(sourceFile);
405
+ lowerModuleForms(sourceFile);
406
+ lowerDisposeSymbols(sourceFile);
407
+ lowerNamespaces(sourceFile);
402
408
  lowerClassExpressions(sourceFile);
403
409
  lowerWeakCollections(sourceFile);
404
410
  lowerGlobalThis(sourceFile);
@@ -441,6 +447,26 @@ function lowerIndexSignatures(node) {
441
447
  return;
442
448
  }
443
449
  const members = node.members ?? [];
450
+ // { (a: A): R } / { new (a: A): C } → (a: A) => R / (a: A) => C
451
+ const isSignature = (m) => m.kind === 'CallSignature' || m.kind === 'ConstructSignature';
452
+ if (members.length === 1 && isSignature(members[0]) && (node.kind === 'TypeLiteral' || node.kind === 'InterfaceDeclaration')) {
453
+ const fnType = { kind: 'FunctionType', parameters: members[0].parameters ?? [], type: members[0].type };
454
+ if (node.kind === 'TypeLiteral') {
455
+ for (const key of Object.keys(node))
456
+ if (key !== 'parent')
457
+ delete node[key];
458
+ node.kind = 'FunctionType';
459
+ node.parameters = fnType.parameters;
460
+ node.type = fnType.type;
461
+ }
462
+ else {
463
+ delete node.members;
464
+ delete node.heritageClauses;
465
+ node.kind = 'TypeAliasDeclaration';
466
+ node.type = fnType;
467
+ }
468
+ return;
469
+ }
444
470
  if (members.length !== 1 || members[0].kind !== 'IndexSignature')
445
471
  return;
446
472
  const signature = members[0];
@@ -465,6 +491,192 @@ function makeRecordReference(node, keyType, valueType) {
465
491
  node.typeName = typeName;
466
492
  node.typeArguments = [keyType, valueType];
467
493
  }
494
+ function lowerAmbientDeclarations(node) {
495
+ for (const child of childNodes(node))
496
+ lowerAmbientDeclarations(child);
497
+ if (!Array.isArray(node.statements))
498
+ return;
499
+ const isDeclare = (m) => m.kind === 'DeclareKeyword';
500
+ node.statements = node.statements.filter((statement) => {
501
+ if (statement.kind === 'VariableStatement' && statement.modifiers) {
502
+ // declare / export on variables don't change the emitted Go
503
+ statement.modifiers = statement.modifiers.filter((m) => !isDeclare(m) && m.kind !== 'ExportKeyword');
504
+ return true;
505
+ }
506
+ return !(statement.modifiers ?? []).some(isDeclare);
507
+ });
508
+ }
509
+ // Every module form becomes an import declaration (all files share one Go package):
510
+ // export { a, b as c } from './m' → import { a, b as c } from './m'
511
+ // export * from './m' → import './m'
512
+ // import x = require('./m') → import * as x from './m'
513
+ // const x = await import('./m') → import * as x from './m'
514
+ function lowerModuleForms(node) {
515
+ for (const child of childNodes(node))
516
+ lowerModuleForms(child);
517
+ if (!Array.isArray(node.statements))
518
+ return;
519
+ node.statements = node.statements.map((statement) => {
520
+ const namespaceImport = (name, specifier) => ({
521
+ kind: 'ImportDeclaration',
522
+ moduleSpecifier: specifier,
523
+ importClause: { kind: 'ImportClause', namedBindings: { kind: 'NamespaceImport', name } }
524
+ });
525
+ if (statement.kind === 'ExportDeclaration' && statement.moduleSpecifier && !statement.isTypeOnly) {
526
+ const clause = statement.exportClause;
527
+ if (clause?.kind === 'NamespaceExport')
528
+ return namespaceImport(clause.name, statement.moduleSpecifier);
529
+ const importClause = clause
530
+ ? { kind: 'ImportClause', namedBindings: { kind: 'NamedImports', elements: clause.elements ?? [] } }
531
+ : undefined;
532
+ return { kind: 'ImportDeclaration', moduleSpecifier: statement.moduleSpecifier, importClause };
533
+ }
534
+ if (statement.kind === 'ImportEqualsDeclaration' && statement.moduleReference?.kind === 'ExternalModuleReference') {
535
+ return namespaceImport(statement.name, statement.moduleReference.expression);
536
+ }
537
+ const declarations = statement.kind === 'VariableStatement' ? (statement.declarationList?.declarations ?? []) : [];
538
+ const init = declarations.length === 1 ? declarations[0].initializer : undefined;
539
+ const call = init?.kind === 'AwaitExpression' ? init.expression : undefined;
540
+ if (call?.kind === 'CallExpression' && call.expression?.kind === 'ImportKeyword' && isStringLiteral(call.arguments?.[0]) && isIdentifier(declarations[0].name)) {
541
+ return namespaceImport(declarations[0].name, call.arguments[0]);
542
+ }
543
+ return statement;
544
+ });
545
+ }
546
+ // Names whose identifier is a property / member name, not a reference
547
+ const MEMBER_NAME_PARENTS = new Set([
548
+ 'PropertyAccessExpression', 'PropertyAssignment', 'PropertySignature', 'MethodSignature',
549
+ 'PropertyDeclaration', 'MethodDeclaration', 'GetAccessor', 'SetAccessor', 'EnumMember'
550
+ ]);
551
+ // Top-level names a statement list declares
552
+ function getDeclaredNames(statements) {
553
+ const names = [];
554
+ for (const statement of statements) {
555
+ if (statement.kind === 'VariableStatement') {
556
+ for (const d of statement.declarationList?.declarations ?? [])
557
+ if (isIdentifier(d.name))
558
+ names.push(d.name.text);
559
+ }
560
+ else if (isIdentifier(statement.name)) {
561
+ names.push(statement.name.text);
562
+ }
563
+ }
564
+ return names;
565
+ }
566
+ // Renames references to the given names (not property/member names) to prefix_name
567
+ function renameIdentifiers(node, names, prefix) {
568
+ for (const key of Object.keys(node)) {
569
+ if (key === 'parent')
570
+ continue;
571
+ const value = node[key];
572
+ const children = Array.isArray(value) ? value : value && typeof value === 'object' ? [value] : [];
573
+ for (const child of children) {
574
+ if (!child || typeof child.kind !== 'string')
575
+ continue;
576
+ if (isIdentifier(child) && names.has(child.text)) {
577
+ if (key === 'name' && MEMBER_NAME_PARENTS.has(node.kind))
578
+ continue;
579
+ if (key === 'right' && node.kind === 'QualifiedName')
580
+ continue;
581
+ if (node.kind === 'ShorthandPropertyAssignment') {
582
+ // { unit } keeps its key: { unit: N_unit }
583
+ node.kind = 'PropertyAssignment';
584
+ const initializer = { kind: 'Identifier', text: `${prefix}_${child.text}` };
585
+ node.initializer = initializer;
586
+ continue;
587
+ }
588
+ child.text = `${prefix}_${child.text}`;
589
+ }
590
+ else {
591
+ renameIdentifiers(child, names, prefix);
592
+ }
593
+ }
594
+ }
595
+ }
596
+ // Flattens the namespaces in a statement list; returns the namespace names found
597
+ // (nested ones as Outer_Inner)
598
+ function flattenNamespaces(statements) {
599
+ const out = [];
600
+ const namespaces = [];
601
+ for (const statement of statements) {
602
+ const isDeclare = (statement.modifiers ?? []).some((m) => m.kind === 'DeclareKeyword');
603
+ if (statement.kind !== 'ModuleDeclaration' || !isIdentifier(statement.name) || isDeclare) {
604
+ out.push(statement);
605
+ continue;
606
+ }
607
+ const prefix = statement.name.text;
608
+ // namespace A.B { } nests a ModuleDeclaration as the body
609
+ const body = statement.body?.kind === 'ModuleDeclaration' ? [statement.body] : (statement.body?.statements ?? []);
610
+ const inner = flattenNamespaces(body);
611
+ rewriteNamespaceAccess(inner.statements, new Set(inner.namespaces));
612
+ const names = new Set([...getDeclaredNames(inner.statements), ...inner.namespaces]);
613
+ for (const child of inner.statements) {
614
+ const wrapper = { kind: 'Block', statements: [child] };
615
+ renameIdentifiers(wrapper, names, prefix);
616
+ }
617
+ namespaces.push(prefix);
618
+ for (const n of inner.namespaces)
619
+ namespaces.push(`${prefix}_${n}`);
620
+ out.push(...inner.statements);
621
+ }
622
+ return { statements: out, namespaces };
623
+ }
624
+ // N.x / N.Inner.x (expressions) and N.T (types) → the flattened identifiers
625
+ function rewriteNamespaceAccess(nodes, namespaces) {
626
+ const rewrite = (node) => {
627
+ for (const key of Object.keys(node)) {
628
+ if (key === 'parent')
629
+ continue;
630
+ const value = node[key];
631
+ if (Array.isArray(value))
632
+ node[key] = value.map((v) => (v && typeof v.kind === 'string' ? rewrite(v) : v));
633
+ else if (value && typeof value.kind === 'string')
634
+ node[key] = rewrite(value);
635
+ }
636
+ const left = node.kind === 'PropertyAccessExpression' ? node.expression : node.kind === 'QualifiedName' ? node.left : undefined;
637
+ const right = node.kind === 'PropertyAccessExpression' ? node.name : node.kind === 'QualifiedName' ? node.right : undefined;
638
+ if (isIdentifier(left) && namespaces.has(left.text) && isIdentifier(right)) {
639
+ const flattened = { kind: 'Identifier', text: `${left.text}_${right.text}` };
640
+ return flattened;
641
+ }
642
+ return node;
643
+ };
644
+ for (let i = 0; i < nodes.length; i++)
645
+ nodes[i] = rewrite(nodes[i]);
646
+ }
647
+ function lowerNamespaces(sourceFile) {
648
+ const flat = flattenNamespaces(sourceFile.statements ?? []);
649
+ sourceFile.statements = flat.statements;
650
+ if (flat.namespaces.length > 0)
651
+ rewriteNamespaceAccess(sourceFile.statements, new Set(flat.namespaces));
652
+ }
653
+ const DISPOSE_SYMBOLS = { dispose: '__dispose', asyncDispose: '__asyncDispose' };
654
+ function getDisposeSymbolName(expr) {
655
+ if (!isPropertyAccessExpression(expr) || !isIdentifier(expr.expression) || expr.expression.text !== 'Symbol')
656
+ return undefined;
657
+ return DISPOSE_SYMBOLS[expr.name.text];
658
+ }
659
+ // [Symbol.dispose]() { … } → __dispose() { … }; x[Symbol.dispose]() → x.__dispose()
660
+ function lowerDisposeSymbols(node) {
661
+ for (const child of childNodes(node))
662
+ lowerDisposeSymbols(child);
663
+ if (node.name?.kind === 'ComputedPropertyName') {
664
+ const name = getDisposeSymbolName(node.name.expression);
665
+ if (name) {
666
+ const identifier = { kind: 'Identifier', text: name };
667
+ node.name = identifier;
668
+ }
669
+ }
670
+ if (node.kind === 'ElementAccessExpression') {
671
+ const name = getDisposeSymbolName(node.argumentExpression);
672
+ if (name) {
673
+ const identifier = { kind: 'Identifier', text: name };
674
+ node.kind = 'PropertyAccessExpression';
675
+ node.name = identifier;
676
+ delete node.argumentExpression;
677
+ }
678
+ }
679
+ }
468
680
  // const Point = class { … } → class Point { … } (the variable names the class)
469
681
  function lowerClassExpressions(node) {
470
682
  for (const child of childNodes(node))
@@ -577,9 +789,12 @@ const usedHelpers = new Set();
577
789
  // per-file import lists can exclude them (helpers are emitted once, in the
578
790
  // main file, which carries these imports instead).
579
791
  const helperProvidedPackages = new Set();
792
+ // Whether the file being parsed is .tsx
793
+ let currentFileIsTsx = false;
580
794
  export function transpileToNative(code, options) {
581
795
  fileResolver = options?.readFile ?? null;
582
796
  parseFunction = options?.parse ?? defaultParseFunction;
797
+ currentFileIsTsx = !!options?.tsx;
583
798
  declaredFunctions.clear();
584
799
  declaredInterfaces.clear();
585
800
  declaredTypeAliases.clear();
@@ -685,6 +900,9 @@ function visit(node, options = {}) {
685
900
  .filter((n) => !!n)
686
901
  .join(options.inline ? '' : '\n\t');
687
902
  }
903
+ else if (isJsxNode(node)) {
904
+ return visitJsx(node);
905
+ }
688
906
  else if (isIdentifier(node) && node.text === 'globalThis') {
689
907
  useHelper('globalThis');
690
908
  return 'TnGlobalThis';
@@ -708,6 +926,9 @@ function visit(node, options = {}) {
708
926
  const goAlias = importAliases.get(node.text);
709
927
  if (goAlias)
710
928
  return goAlias;
929
+ // A class passed as a value (make(Box)) is its constructor function
930
+ if (classNames.has(node.text) && isValuePosition(node))
931
+ return `New${getSafeName(node.text)}`;
711
932
  // Array references: reads go through the shared slice-header pointer
712
933
  if (referenceArrays.has(node.text)) {
713
934
  return `(*${getSafeName(node.text)})`;
@@ -840,6 +1061,8 @@ function visit(node, options = {}) {
840
1061
  if (regexProperty)
841
1062
  return regexProperty;
842
1063
  // import.meta.url — the file URL of the running program
1064
+ if (isNewTargetNode(node.expression) && node.name.text === 'name')
1065
+ return '__newTarget';
843
1066
  if (node.expression.kind === 'MetaProperty' && node.name.text === 'url') {
844
1067
  useHelper('moduleUrl');
845
1068
  return 'TnModuleURL()';
@@ -1197,6 +1420,9 @@ function visit(node, options = {}) {
1197
1420
  const promiseCall = visitPromiseCall(node);
1198
1421
  if (promiseCall)
1199
1422
  return promiseCall;
1423
+ const functionCall = visitFunctionCallApply(node);
1424
+ if (functionCall)
1425
+ return functionCall;
1200
1426
  const objectCall = visitObjectCall(node);
1201
1427
  if (objectCall)
1202
1428
  return objectCall;
@@ -1389,6 +1615,11 @@ function visit(node, options = {}) {
1389
1615
  return op === '==' ? eq : `!(${eq})`;
1390
1616
  }
1391
1617
  }
1618
+ // new.target === C → the target class name is C's
1619
+ if ((op === '==' || op === '!=') && (isNewTargetNode(node.left) || isNewTargetNode(node.right))) {
1620
+ const other = isNewTargetNode(node.left) ? node.right : node.left;
1621
+ return `__newTarget ${op} ${isIdentifier(other) ? toGoStringLiteral(other.text) : '""'}`;
1622
+ }
1392
1623
  // obj[key] = v on any
1393
1624
  if (op === '=' && isElementAccessExpression(node.left) && isDynamicValue(node.left.expression)) {
1394
1625
  useHelper('dynamic');
@@ -1654,7 +1885,7 @@ function visit(node, options = {}) {
1654
1885
  if (options.isOutside) {
1655
1886
  // async functions run their body in a goroutine sending over a channel
1656
1887
  const isAsync = (node.modifiers ?? []).some((m) => m.kind === 'AsyncKeyword');
1657
- if (isAsync && node.name) {
1888
+ if (isAsync && node.name && !node.asteriskToken) {
1658
1889
  return emitChannelFunction(node, true);
1659
1890
  }
1660
1891
  if (node.asteriskToken && node.name) {
@@ -1698,6 +1929,10 @@ function visit(node, options = {}) {
1698
1929
  else if (node.kind === 'ThisKeyword') {
1699
1930
  if (thisOverrideName && getEnclosingFunction(node) === thisOverrideFn)
1700
1931
  return thisOverrideName;
1932
+ if (isThisOfPlainFunction(node)) {
1933
+ useHelper('decorators');
1934
+ return 'TnThis';
1935
+ }
1701
1936
  return 'self';
1702
1937
  }
1703
1938
  else if (node.kind === 'PrivateIdentifier') {
@@ -1709,6 +1944,8 @@ function visit(node, options = {}) {
1709
1944
  enumNames.add(enumName);
1710
1945
  enumBaseTypes.set(enumName, getEnumBaseType(node));
1711
1946
  if (options.addFunctionOutside) {
1947
+ if (enumHasComputedMember(node))
1948
+ return visitEnumDeclaration(node, true);
1712
1949
  outsideNodes.push(node);
1713
1950
  return '';
1714
1951
  }
@@ -1796,7 +2033,7 @@ function visit(node, options = {}) {
1796
2033
  return `type ${name}${typeParams} struct {\n${fields.join('\n')}\n}`;
1797
2034
  }
1798
2035
  const members = [...extendedInterfaces.map((e) => `\t${e}`), ...methods];
1799
- return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}`;
2036
+ return `type ${name}${typeParams} interface {\n${members.join('\n')}\n}${extendedInterfaces.length === 0 ? emitInterfaceAdapter(node) : ''}`;
1800
2037
  }
1801
2038
  else if (isClassDeclaration(node)) {
1802
2039
  if (options.addFunctionOutside) {
@@ -1880,7 +2117,7 @@ function visit(node, options = {}) {
1880
2117
  }
1881
2118
  classPropertyTypes.set(className, properties);
1882
2119
  classMethodReturnTypes.set(className, methods);
1883
- return '';
2120
+ return visitClassDecorators(node);
1884
2121
  }
1885
2122
  const name = visit(node.name);
1886
2123
  const typeParams = getTypeParameters(node.typeParameters);
@@ -1936,7 +2173,7 @@ function visit(node, options = {}) {
1936
2173
  const args = (s.expression.arguments ?? []).map((a) => visit(a));
1937
2174
  if (extendsError)
1938
2175
  return `self.message = ${args[0] ?? '""'}\n\t\t`;
1939
- return `self.${goFieldName(parentClass)} = *New${parentClass}(${args.join(', ')})\n\t\t`;
2176
+ return `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, args)}\n\t\t`;
1940
2177
  }
1941
2178
  return visit(s);
1942
2179
  })
@@ -1945,17 +2182,19 @@ function visit(node, options = {}) {
1945
2182
  .map((p) => `self.${goFieldName(p.name.text)} = ${visit(p.name)}\n\t\t`)
1946
2183
  .join('');
1947
2184
  const parentInit = parentClass && !extendsError && !superCalledInCtor
1948
- ? `self.${goFieldName(parentClass)} = *New${parentClass}()\n\t\t`
2185
+ ? `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, [])}\n\t\t`
1949
2186
  : '';
1950
- result += `func New${name}${typeParams}(${ctorParameterInfo.signature}) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${ctorParameterInfo.prefixBlockContent}${parentInit}${fieldInitializers(node)}${parameterAssignments}${bodyStatements}return self;\n\t}\n\n`;
2187
+ const ctorBody = `{\n\t\tself := &${name}${typeParamNames}{}\n\t\t${ctorParameterInfo.prefixBlockContent}${parentInit}${fieldInitializers(node)}${parameterAssignments}${bodyStatements}return self;\n\t}`;
2188
+ result += emitConstructor(name, typeParams, typeParamNames, ctorParameterInfo.signature, ctorBody);
1951
2189
  }
1952
2190
  else if (extendsError) {
1953
2191
  // class X extends Error {} — the constructor takes the message
1954
2192
  result += `func New${name}${typeParams}(message string) *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\tself.message = message\n\t\t${fieldInitializers(node)}return self\n\t}\n\n`;
1955
2193
  }
1956
2194
  else {
1957
- const parentInit = parentClass && !extendsError ? `self.${goFieldName(parentClass)} = *New${parentClass}()\n\t\t` : '';
1958
- result += `func New${name}${typeParams}() *${name}${typeParamNames} {\n\t\tself := &${name}${typeParamNames}{}\n\t\t${parentInit}${fieldInitializers(node)}return self\n\t}\n\n`;
2195
+ const parentInit = parentClass && !extendsError ? `self.${goFieldName(parentClass)} = *${parentConstructorCall(parentClass, [])}\n\t\t` : '';
2196
+ const ctorBody = `{\n\t\tself := &${name}${typeParamNames}{}\n\t\t${parentInit}${fieldInitializers(node)}return self\n\t}`;
2197
+ result += emitConstructor(name, typeParams, typeParamNames, '', ctorBody);
1959
2198
  }
1960
2199
  // Inherited concrete methods are copied onto the subclass: Go embedding
1961
2200
  // promotes the parent's receiver, which would break `this` dispatch
@@ -2007,6 +2246,10 @@ function visit(node, options = {}) {
2007
2246
  if (!methodName || ownMethodNames.has(methodName))
2008
2247
  continue;
2009
2248
  ownMethodNames.add(methodName);
2249
+ if (getDecorators(member).length > 0) {
2250
+ copiedMethods.push(emitDecoratedMethod(name, typeParamNames, member, visit(ancestor.name)));
2251
+ continue;
2252
+ }
2010
2253
  const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
2011
2254
  const returnType = member.type ? ` ${getType(member.type)}` : '';
2012
2255
  copiedMethods.push(`func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n`);
@@ -2053,7 +2296,10 @@ function visit(node, options = {}) {
2053
2296
  continue; // abstract method — implemented by subclasses
2054
2297
  if (isAbstractClass && !isStatic)
2055
2298
  continue; // only reachable via subclasses
2056
- if (isStatic) {
2299
+ if (getDecorators(member).length > 0) {
2300
+ result += emitDecoratedMethod(name, typeParamNames, member, name);
2301
+ }
2302
+ else if (isStatic) {
2057
2303
  // Static methods become package-level functions named ClassName_methodName
2058
2304
  result += `func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
2059
2305
  }
@@ -2140,6 +2386,10 @@ function visit(node, options = {}) {
2140
2386
  }
2141
2387
  const typeArgs = getTypeArguments((node.typeArguments ?? []));
2142
2388
  const args = node.arguments ? (node.arguments ?? []).map((a) => visit(a)) : [];
2389
+ // new F(…) where F holds a constructor function (construct signature)
2390
+ if (!classNames.has(className) && inferExpressionType(node.expression)?.startsWith('func(')) {
2391
+ return `${className}(${args.join(', ')})`;
2392
+ }
2143
2393
  return `New${className}${typeArgs}(${args.join(', ')})`;
2144
2394
  }
2145
2395
  else if (isObjectLiteralExpression(node)) {
@@ -2181,6 +2431,8 @@ function visit(node, options = {}) {
2181
2431
  ? getTypeArguments(contextualType.typeArguments)
2182
2432
  : '';
2183
2433
  const structName = typeName.replace(/^\*/, '') + (typeName.endsWith(']') ? '' : typeArgs);
2434
+ if (isMethodInterfaceName(typeName.replace(/^\*/, '')))
2435
+ return visitInterfaceLiteral(node, typeName.replace(/^\*/, ''), typeArgs);
2184
2436
  // { ...base, x: 1 } → a copy of *base with fields set
2185
2437
  if ((node.properties ?? []).some((p) => p.kind === 'SpreadAssignment')) {
2186
2438
  const steps = (node.properties ?? []).map((p) => {
@@ -2245,6 +2497,8 @@ function visit(node, options = {}) {
2245
2497
  // Visits a value going into a slot of Go type `goType`; non-nil values for
2246
2498
  // nullable primitives (*string/*float64/*bool) are boxed into pointers
2247
2499
  function toGoValueOfType(expr, goType) {
2500
+ if (goType === 'string' && inferExpressionType(expr) === 'TnJsxNode')
2501
+ return `string(${visit(expr)})`;
2248
2502
  if (goType &&
2249
2503
  ((isObjectLiteralExpression(expr) && (expr.properties ?? []).length === 0 && goType.startsWith('*TnMap[')) ||
2250
2504
  (isArrayLiteralExpression(expr) && (expr.elements ?? []).length === 0 && goType.startsWith('[]')))) {
@@ -2458,7 +2712,12 @@ function alwaysExits(statement) {
2458
2712
  function visitBlockStatements(statements) {
2459
2713
  const added = [];
2460
2714
  const parts = [];
2461
- for (const statement of statements) {
2715
+ for (let index = 0; index < statements.length; index++) {
2716
+ const statement = statements[index];
2717
+ if (isUsingDeclaration(statement)) {
2718
+ parts.push(visitUsingDeclaration(statement, statements.slice(index + 1)));
2719
+ break;
2720
+ }
2462
2721
  parts.push(visit(statement));
2463
2722
  if (isIfStatement(statement) && !statement.elseStatement && alwaysExits(statement.thenStatement)) {
2464
2723
  for (const name of getNarrowedNames(statement.expression, false)) {
@@ -2473,6 +2732,39 @@ function visitBlockStatements(statements) {
2473
2732
  narrowedVariables.delete(name);
2474
2733
  return parts.join('\t');
2475
2734
  }
2735
+ function isUsingDeclaration(statement) {
2736
+ const kind = statement.kind === 'VariableStatement' ? statement.declarationList?.declarationKind : undefined;
2737
+ return kind === 'using' || kind === 'await using';
2738
+ }
2739
+ // using x = r; …rest → x := r; defer dispose(x); …rest. In a function body Go's
2740
+ // defer is the block end; nested blocks run the rest in a closure so disposal
2741
+ // happens when the block ends (return/break/continue there keep the defer form)
2742
+ function visitUsingDeclaration(statement, rest) {
2743
+ const isAwait = statement.declarationList.declarationKind === 'await using';
2744
+ const disposals = (statement.declarationList.declarations ?? [])
2745
+ .filter((d) => isIdentifier(d.name))
2746
+ .map((d) => {
2747
+ const name = visit(d.name);
2748
+ const goType = inferExpressionType(d.initializer) ?? '';
2749
+ const className = goType.replace(/^\*/, '').replace(/\[.*$/, '');
2750
+ const classNode = declaredClasses.get(className);
2751
+ const hasAsync = (classNode?.members ?? []).some((m) => m.name?.text === '__asyncDispose');
2752
+ if (isAwait && hasAsync)
2753
+ useHelper('promise');
2754
+ const call = isAwait && hasAsync ? `TnAwait(${name}.__asyncDispose())` : `${name}.__dispose()`;
2755
+ return `defer func() { if ${name} != nil { ${call} } }()`;
2756
+ })
2757
+ .reverse();
2758
+ const declaration = { ...statement, declarationList: { ...statement.declarationList, declarationKind: 'const' } };
2759
+ const head = `${visit(declaration)}${disposals.join('\n\t\t')}\n\t\t`;
2760
+ const block = statement.parent;
2761
+ const isFunctionBody = !!block?.parent && block.parent.body === block;
2762
+ const restBlock = { kind: 'Block', statements: rest };
2763
+ const exits = containsNode(restBlock, (n) => n.kind === 'ReturnStatement' || n.kind === 'BreakStatement' || n.kind === 'ContinueStatement');
2764
+ if (isFunctionBody || exits)
2765
+ return head + visitBlockStatements(rest);
2766
+ return `func() {\n\t\t${head}${visitBlockStatements(rest)}}()\n\t`;
2767
+ }
2476
2768
  function visitDynamicArithmetic(node, op) {
2477
2769
  const leftDynamic = isDynamicValue(node.left);
2478
2770
  const rightDynamic = isDynamicValue(node.right);
@@ -2677,6 +2969,66 @@ function visitSpreadCallArguments(node) {
2677
2969
  return result;
2678
2970
  }
2679
2971
  // self.field = initializer for each instance field that has one
2972
+ // Whether a class's constructor chain (its own or an ancestor's) reads new.target
2973
+ function usesNewTarget(className) {
2974
+ let current = className;
2975
+ for (let depth = 0; depth < 50; depth++) {
2976
+ const classNode = declaredClasses.get(current);
2977
+ if (!classNode)
2978
+ return false;
2979
+ const ctor = (classNode.members ?? []).find((m) => isConstructorDeclaration(m));
2980
+ if (ctor?.body && containsNode(ctor.body, (n) => isNewTargetNode(n)))
2981
+ return true;
2982
+ const extendsClause = (classNode.heritageClauses ?? []).find((c) => c.token === 'ExtendsKeyword');
2983
+ if (!extendsClause)
2984
+ return false;
2985
+ current = visit(extendsClause.types[0].expression);
2986
+ }
2987
+ return false;
2988
+ }
2989
+ function isNewTargetNode(node) {
2990
+ return node?.kind === 'MetaProperty' && node.name?.text === 'target';
2991
+ }
2992
+ // The parent part of a subclass constructor: new.target-aware chains pass the target on
2993
+ function parentConstructorCall(parentClass, args) {
2994
+ if (!usesNewTarget(parentClass))
2995
+ return `New${parentClass}(${args.join(', ')})`;
2996
+ return `new${parentClass}As(${['__newTarget', ...args].join(', ')})`;
2997
+ }
2998
+ // Splits a Go parameter list at top-level commas
2999
+ function splitGoParameters(signature) {
3000
+ const parts = [];
3001
+ let depth = 0;
3002
+ let current = '';
3003
+ for (const ch of signature) {
3004
+ if (ch === '(' || ch === '[' || ch === '{')
3005
+ depth++;
3006
+ if (ch === ')' || ch === ']' || ch === '}')
3007
+ depth--;
3008
+ if (ch === ',' && depth === 0) {
3009
+ parts.push(current.trim());
3010
+ current = '';
3011
+ }
3012
+ else
3013
+ current += ch;
3014
+ }
3015
+ if (current.trim())
3016
+ parts.push(current.trim());
3017
+ return parts;
3018
+ }
3019
+ // NewX(…) — or, for new.target-aware classes, newXAs(__newTarget string, …) plus a
3020
+ // NewX(…) that calls it with "X"
3021
+ function emitConstructor(name, typeParams, typeParamNames, signature, body) {
3022
+ if (!usesNewTarget(name))
3023
+ return `func New${name}${typeParams}(${signature}) *${name}${typeParamNames} ${body}\n\n`;
3024
+ const forward = splitGoParameters(signature).map((p) => {
3025
+ const [paramName, paramType] = [p.slice(0, p.indexOf(' ')), p.slice(p.indexOf(' ') + 1)];
3026
+ return paramType.startsWith('...') ? `${paramName}...` : paramName;
3027
+ });
3028
+ const internal = ['__newTarget string', signature].filter((part) => part).join(', ');
3029
+ return (`func new${name}As${typeParams}(${internal}) *${name}${typeParamNames} ${body}\n\n` +
3030
+ `func New${name}${typeParams}(${signature}) *${name}${typeParamNames} {\n\t\treturn new${name}As${typeParamNames}(${[toGoStringLiteral(name), ...forward].join(', ')})\n\t}\n\n`);
3031
+ }
2680
3032
  function fieldInitializers(classNode) {
2681
3033
  return (classNode.members ?? [])
2682
3034
  .filter((m) => isPropertyDeclaration(m) &&
@@ -3598,6 +3950,8 @@ function inferExpressionType(expr) {
3598
3950
  return '*tnbig.Int';
3599
3951
  if (expr.kind === 'TypeOfExpression')
3600
3952
  return 'string';
3953
+ if (isJsxNode(expr))
3954
+ return 'TnJsxNode';
3601
3955
  if (expr.kind === 'TaggedTemplateExpression' && isStringRawTag(expr.tag))
3602
3956
  return 'string';
3603
3957
  if (isRegularExpressionLiteral(expr)) {
@@ -3673,6 +4027,8 @@ function inferExpressionType(expr) {
3673
4027
  return '[]string';
3674
4028
  }
3675
4029
  // import.meta.url is a string
4030
+ if (isPropertyAccessExpression(expr) && isNewTargetNode(expr.expression) && expr.name.text === 'name')
4031
+ return 'string';
3676
4032
  if (isPropertyAccessExpression(expr) && expr.expression.kind === 'MetaProperty' && expr.name.text === 'url') {
3677
4033
  return 'string';
3678
4034
  }
@@ -3751,6 +4107,9 @@ function inferExpressionType(expr) {
3751
4107
  const regexPropertyType = inferRegexPropertyType(expr);
3752
4108
  if (regexPropertyType)
3753
4109
  return regexPropertyType;
4110
+ if (isPropertyAccessExpression(expr) && inferExpressionType(expr.expression) === '*TnDecoratorContext') {
4111
+ return ['static', 'private'].includes(expr.name.text) ? 'bool' : 'string';
4112
+ }
3754
4113
  if (isPropertyAccessExpression(expr) && expr.name.text === 'length' && hasDynamicProperties(expr.expression)) {
3755
4114
  return 'float64';
3756
4115
  }
@@ -3813,6 +4172,9 @@ function inferExpressionType(expr) {
3813
4172
  // Numeric enums are float64 constants; string enums keep their type
3814
4173
  if (enumBaseTypes.get(expr.expression.text) === 'float64')
3815
4174
  return 'float64';
4175
+ if (enumBaseTypes.get(expr.expression.text) === 'interface{}') {
4176
+ return enumMemberGoTypes.get(`${expr.expression.text}.${getEnumMemberName(expr.name)}`) ?? 'interface{}';
4177
+ }
3816
4178
  const enumType = getSafeName(expr.expression.text);
3817
4179
  return enumType;
3818
4180
  }
@@ -4206,6 +4568,7 @@ const BUILTIN_FUNCTION_TYPES = {
4206
4568
  const STRING_METHOD_RETURN_TYPES = {
4207
4569
  trim: 'string',
4208
4570
  trimStart: 'string',
4571
+ normalize: 'string',
4209
4572
  trimEnd: 'string',
4210
4573
  toUpperCase: 'string',
4211
4574
  toLowerCase: 'string',
@@ -4360,6 +4723,46 @@ function makeNullableType(typeName) {
4360
4723
  // property-only interfaces / object type aliases
4361
4724
  // Object types that compile to Go structs: property-only interfaces and
4362
4725
  // object type aliases (values of these types are *Name, like JS references)
4726
+ // A declared interface with method signatures (a Go interface type)
4727
+ function isMethodInterfaceName(name) {
4728
+ return (declaredInterfaces.get(name)?.members ?? []).some((m) => isMethodSignature(m));
4729
+ }
4730
+ // type P_obj struct { get_fn func() T; … } + func (o *P_obj) get() T { return o.get_fn() }
4731
+ function emitInterfaceAdapter(node) {
4732
+ const name = visit(node.name);
4733
+ const typeParams = getTypeParameters(node.typeParameters);
4734
+ const typeParamNames = getTypeParameterNames(node.typeParameters);
4735
+ const fields = [];
4736
+ const methods = [];
4737
+ for (const member of node.members ?? []) {
4738
+ if (!isMethodSignature(member))
4739
+ continue;
4740
+ const methodName = visit(member.name);
4741
+ const params = (member.parameters ?? []).map((p) => `${visit(p.name)} ${getType(p.type)}`);
4742
+ const args = (member.parameters ?? []).map((p) => visit(p.name));
4743
+ const returnType = member.type ? getType(member.type) : '';
4744
+ const signature = `func(${params.join(', ')})${returnType ? ` ${returnType}` : ''}`;
4745
+ fields.push(`\t${methodName}_fn ${signature}`);
4746
+ methods.push(`func (o *${name}_obj${typeParamNames}) ${methodName}(${params.join(', ')})${returnType ? ` ${returnType}` : ''} { ${returnType ? 'return ' : ''}o.${methodName}_fn(${args.join(', ')}) }`);
4747
+ }
4748
+ return `\n\ntype ${name}_obj${typeParams} struct {\n${fields.join('\n')}\n}\n\n${methods.join('\n\n')}`;
4749
+ }
4750
+ // { get: () => 4, set(v) { … } } typed as a method interface → &P_obj{get_fn: …, set_fn: …}
4751
+ function visitInterfaceLiteral(node, interfaceName, typeArgs) {
4752
+ const fields = (node.properties ?? []).map((p) => {
4753
+ const fieldName = `${visit(p.name)}_fn`;
4754
+ if (isPropertyAssignment(p))
4755
+ return `${fieldName}: ${visit(p.initializer)}`;
4756
+ if (isShorthandPropertyAssignment(p))
4757
+ return `${fieldName}: ${visit(p.name)}`;
4758
+ if (isMethodDeclaration(p)) {
4759
+ const fn = { kind: 'FunctionExpression', parameters: p.parameters, type: p.type, body: p.body, parent: p.parent };
4760
+ return `${fieldName}: ${visit(fn)}`;
4761
+ }
4762
+ return '';
4763
+ });
4764
+ return `&${interfaceName}_obj${typeArgs}${compositeBody(fields.filter((f) => f))}`;
4765
+ }
4363
4766
  function isObjectTypeName(name) {
4364
4767
  const alias = declaredTypeAliases.get(name) ?? typeAliases.get(name);
4365
4768
  if (alias)
@@ -4904,16 +5307,23 @@ function getEnumMemberName(name) {
4904
5307
  }
4905
5308
  return 'Member';
4906
5309
  }
5310
+ // string enums, numeric enums, or interface{} for heterogeneous ones (each member keeps its own type)
4907
5311
  function getEnumBaseType(node) {
5312
+ let hasString = false;
5313
+ let hasNumber = false;
4908
5314
  for (const member of (node.members ?? [])) {
4909
5315
  const initializer = member.initializer;
4910
- if (!initializer)
4911
- continue;
4912
- if (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) {
4913
- return 'string';
5316
+ if (initializer && (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer))) {
5317
+ hasString = true;
4914
5318
  }
5319
+ else {
5320
+ hasNumber = true;
5321
+ }
5322
+ enumMemberGoTypes.set(`${node.name.text}.${getEnumMemberName(member.name)}`, initializer && (isStringLiteral(initializer) || isNoSubstitutionTemplateLiteral(initializer)) ? 'string' : 'float64');
4915
5323
  }
4916
- return 'float64';
5324
+ if (hasString && hasNumber)
5325
+ return 'interface{}';
5326
+ return hasString ? 'string' : 'float64';
4917
5327
  }
4918
5328
  function readNumericEnumInitializer(initializer) {
4919
5329
  if (isNumericLiteral(initializer)) {
@@ -4926,18 +5336,28 @@ function readNumericEnumInitializer(initializer) {
4926
5336
  }
4927
5337
  return undefined;
4928
5338
  }
4929
- function visitEnumDeclaration(node) {
5339
+ function enumHasComputedMember(node) {
5340
+ return (node.members ?? []).some((m) => m.initializer &&
5341
+ !isStringLiteral(m.initializer) &&
5342
+ !isNoSubstitutionTemplateLiteral(m.initializer) &&
5343
+ readNumericEnumInitializer(m.initializer) === undefined);
5344
+ }
5345
+ function visitEnumDeclaration(node, inPlace = false) {
4930
5346
  const enumName = getSafeName(node.name.text);
4931
5347
  const baseType = enumBaseTypes.get(node.name.text) ?? getEnumBaseType(node);
4932
5348
  let nextNumericValue = 0;
4933
5349
  let canAutoIncrement = true;
5350
+ let hasComputedMember = false;
4934
5351
  const members = [];
4935
5352
  for (const member of (node.members ?? [])) {
4936
5353
  const memberName = getEnumMemberName(member.name);
4937
5354
  const symbolName = `${enumName}_${memberName}`;
4938
5355
  let valueExpr;
4939
5356
  if (member.initializer) {
4940
- if (baseType === 'float64') {
5357
+ if (baseType === 'interface{}' && (isStringLiteral(member.initializer) || isNoSubstitutionTemplateLiteral(member.initializer))) {
5358
+ valueExpr = visit(member.initializer);
5359
+ }
5360
+ else if (baseType !== 'string') {
4941
5361
  const numericValue = readNumericEnumInitializer(member.initializer);
4942
5362
  if (numericValue !== undefined) {
4943
5363
  valueExpr = `${numericValue}`;
@@ -4947,13 +5367,14 @@ function visitEnumDeclaration(node) {
4947
5367
  else {
4948
5368
  valueExpr = `float64(${visit(member.initializer)})`;
4949
5369
  canAutoIncrement = false;
5370
+ hasComputedMember = true;
4950
5371
  }
4951
5372
  }
4952
5373
  else {
4953
5374
  valueExpr = visit(member.initializer);
4954
5375
  }
4955
5376
  }
4956
- else if (baseType === 'float64') {
5377
+ else if (baseType !== 'string') {
4957
5378
  const currentValue = canAutoIncrement ? nextNumericValue : 0;
4958
5379
  valueExpr = `${currentValue}`;
4959
5380
  nextNumericValue = currentValue + 1;
@@ -4963,10 +5384,37 @@ function visitEnumDeclaration(node) {
4963
5384
  }
4964
5385
  members.push(`\t${symbolName} ${enumName} = ${enumName}(${valueExpr})`);
4965
5386
  }
4966
- // Numeric enums are plain float64 constants so they compare against numbers;
4967
- // string enums keep a named type
5387
+ // Assigned in place: package variables declared, values set in source order
5388
+ if (inPlace) {
5389
+ return members
5390
+ .map((m) => {
5391
+ const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
5392
+ const raw = m.slice(m.indexOf(`= ${enumName}(`) + enumName.length + 3, -1);
5393
+ const value = /^-?[0-9.]+$/.test(raw) ? `float64(${raw})` : raw;
5394
+ const memberType = baseType === 'interface{}'
5395
+ ? (enumMemberGoTypes.get(`${node.name.text}.${name.slice(enumName.length + 1)}`) ?? 'interface{}')
5396
+ : baseType === 'float64'
5397
+ ? 'float64'
5398
+ : enumName;
5399
+ mainPackageVariables.push(`var ${name} ${memberType}`);
5400
+ return `${name} = ${memberType === enumName ? `${enumName}(${raw})` : value};\n\t`;
5401
+ })
5402
+ .join('');
5403
+ }
5404
+ // Heterogeneous enums: plain variables of each member's own type
5405
+ if (baseType === 'interface{}') {
5406
+ return `var (\n${members
5407
+ .map((m) => {
5408
+ const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
5409
+ const value = m.slice(m.indexOf(`= ${enumName}(`) + enumName.length + 3, -1);
5410
+ return `\t${name} = ${/^-?[0-9.]+$/.test(value) ? `float64(${value})` : value}`;
5411
+ })
5412
+ .join('\n')}\n)`;
5413
+ }
5414
+ // Numeric enums are plain float64 constants so they compare against numbers
5415
+ // (variables when a member is computed); string enums keep a named type
4968
5416
  if (baseType === 'float64') {
4969
- return `const (\n${members
5417
+ return `${hasComputedMember ? 'var' : 'const'} (\n${members
4970
5418
  .map((m) => {
4971
5419
  const name = m.slice(m.indexOf('\t') + 1, m.indexOf(' '));
4972
5420
  const value = m.slice(m.lastIndexOf('=') + 1).trim();
@@ -4991,6 +5439,17 @@ function getType(typeNode, getArrayType = false) {
4991
5439
  }
4992
5440
  if (typeNode.kind === 'MappedType')
4993
5441
  return getMappedType(typeNode);
5442
+ if (isTypeReferenceNode(typeNode) && isIdentifier(typeNode.typeName) && /DecoratorContext$/.test(typeNode.typeName.text)) {
5443
+ useHelper('decorators');
5444
+ return '*TnDecoratorContext';
5445
+ }
5446
+ if (isTypeReferenceNode(typeNode) &&
5447
+ typeNode.typeName?.kind === 'QualifiedName' &&
5448
+ typeNode.typeName.left?.text === 'JSX' &&
5449
+ typeNode.typeName.right?.text === 'Element') {
5450
+ useHelper('jsx');
5451
+ return 'TnJsxNode';
5452
+ }
4994
5453
  // readonly T[] → T[]; keyof T → string
4995
5454
  if (typeNode.kind === 'TypeOperator') {
4996
5455
  return typeNode.operator === 'KeyOfKeyword' ? 'string' : getType(typeNode.type, getArrayType);
@@ -5030,8 +5489,12 @@ function getType(typeNode, getArrayType = false) {
5030
5489
  return 'interface{}';
5031
5490
  }
5032
5491
  if (isFunctionTypeNode(typeNode)) {
5492
+ // (...args: T[]) => R is variadic: func(...T) R
5033
5493
  const params = (typeNode.parameters ?? [])
5034
- .map((p) => (p.type ? getType(p.type) : 'interface{}'))
5494
+ .map((p) => {
5495
+ const paramType = p.type ? getType(p.type) : 'interface{}';
5496
+ return p.dotDotDotToken && paramType.startsWith('[]') ? `...${paramType.slice(2)}` : paramType;
5497
+ })
5035
5498
  .join(', ');
5036
5499
  const ret = typeNode.type ? ` ${getType(typeNode.type)}` : '';
5037
5500
  return `func(${params})${ret}`;
@@ -5051,6 +5514,8 @@ function getType(typeNode, getArrayType = false) {
5051
5514
  // Numeric enums lowered to float64 constants have no named type
5052
5515
  if (enumBaseTypes.get(name) === 'float64')
5053
5516
  return 'float64';
5517
+ if (enumBaseTypes.get(name) === 'interface{}')
5518
+ return 'interface{}';
5054
5519
  return getSafeName(name);
5055
5520
  }
5056
5521
  const aliasType = getAliasType(name);
@@ -5581,6 +6046,15 @@ const stringMethodHandlers = {
5581
6046
  return `${obj}[${sliceIndex(obj, args[0])}:]`;
5582
6047
  },
5583
6048
  concat: (obj, args) => `${obj} + ${args.join(' + ')}`,
6049
+ // normalize('NFC' | 'NFD' | 'NFKC' | 'NFKD')
6050
+ normalize: (obj, args) => {
6051
+ importedPackages.add('golang.org/x/text/unicode/norm');
6052
+ const form = args[0] ?? '"NFC"';
6053
+ const literal = /^"(NFC|NFD|NFKC|NFKD)"$/.exec(form);
6054
+ if (literal)
6055
+ return `tnnorm.${literal[1]}.String(${obj})`;
6056
+ return `func() string { switch ${form} { case "NFD": return tnnorm.NFD.String(${obj}); case "NFKC": return tnnorm.NFKC.String(${obj}); case "NFKD": return tnnorm.NFKD.String(${obj}) }; return tnnorm.NFC.String(${obj}) }()`;
6057
+ },
5584
6058
  padStart: (obj, args) => {
5585
6059
  importedPackages.add('strings');
5586
6060
  const pad = args[1] ?? '" "';
@@ -6186,6 +6660,216 @@ function visitRegexProperty(node) {
6186
6660
  importedPackages.add('strings');
6187
6661
  return `strings.Contains(TnRegexpFlags(${receiver}), "${REGEX_FLAG_PROPERTIES[name]}")`;
6188
6662
  }
6663
+ // Whether an expression is used as a value (argument, initializer, element, assigned or returned)
6664
+ function isValuePosition(node) {
6665
+ const parent = node.parent;
6666
+ if (!parent)
6667
+ return false;
6668
+ if (isCallExpression(parent))
6669
+ return (parent.arguments ?? []).includes(node);
6670
+ if (isVariableDeclaration(parent) || isPropertyAssignment(parent))
6671
+ return parent.initializer === node;
6672
+ if (isArrayLiteralExpression(parent))
6673
+ return true;
6674
+ if (isReturnStatement(parent))
6675
+ return true;
6676
+ return isBinaryExpression(parent) && parent.operatorToken.kind === 'EqualsToken' && parent.right === node;
6677
+ }
6678
+ function isJsxNode(node) {
6679
+ return node?.kind === 'JsxElement' || node?.kind === 'JsxSelfClosingElement' || node?.kind === 'JsxFragment';
6680
+ }
6681
+ // JSX text: lines trimmed at their inner edges, blank lines dropped, joined by a space
6682
+ function cleanJsxText(text) {
6683
+ const lines = text.replace(/\r\n?/g, '\n').split('\n');
6684
+ const kept = [];
6685
+ for (let i = 0; i < lines.length; i++) {
6686
+ let line = lines[i].replace(/\t/g, ' ');
6687
+ if (i > 0)
6688
+ line = line.replace(/^ +/, '');
6689
+ if (i < lines.length - 1)
6690
+ line = line.replace(/ +$/, '');
6691
+ if (line)
6692
+ kept.push(line);
6693
+ }
6694
+ return kept.join(' ');
6695
+ }
6696
+ // The children of an element as []interface{} (text, {expressions}, nested elements)
6697
+ function visitJsxChildren(children) {
6698
+ const parts = [];
6699
+ for (const child of children) {
6700
+ if (child.kind === 'JsxText') {
6701
+ const text = cleanJsxText(child.text ?? '');
6702
+ if (text)
6703
+ parts.push(toGoStringLiteral(text));
6704
+ }
6705
+ else if (child.kind === 'JsxExpression') {
6706
+ if (child.expression)
6707
+ parts.push(visit(child.expression));
6708
+ }
6709
+ else {
6710
+ parts.push(visit(child));
6711
+ }
6712
+ }
6713
+ return `[]interface{}{${parts.join(', ')}}`;
6714
+ }
6715
+ // An attribute's value: name="x", name={expr}, or a bare name (true)
6716
+ function getJsxAttributeValue(attribute) {
6717
+ if (!attribute.initializer) {
6718
+ const trueNode = { kind: 'TrueKeyword', parent: attribute };
6719
+ return trueNode;
6720
+ }
6721
+ return attribute.initializer.kind === 'JsxExpression' ? attribute.initializer.expression : attribute.initializer;
6722
+ }
6723
+ function visitJsx(node) {
6724
+ useHelper('jsx');
6725
+ if (node.kind === 'JsxFragment')
6726
+ return `TnJsxChildren(${visitJsxChildren(node.children ?? [])})`;
6727
+ const opening = node.kind === 'JsxElement' ? node.openingElement : node;
6728
+ const children = node.kind === 'JsxElement' ? (node.children ?? []) : [];
6729
+ const tag = opening.tagName;
6730
+ const attributes = (opening.attributes?.properties ?? []).filter((a) => a.kind === 'JsxAttribute');
6731
+ // <div …> / <my-element …>: an HTML element
6732
+ if (isIdentifier(tag) && (/^[a-z]/.test(tag.text) || tag.text.includes('-'))) {
6733
+ const attrs = attributes.map((a) => `{${toGoStringLiteral(a.name.text)}, ${visit(getJsxAttributeValue(a))}}`);
6734
+ return `TnJsxElement(${toGoStringLiteral(tag.text)}, []TnJsxAttr{${attrs.join(', ')}}, ${visitJsxChildren(children)})`;
6735
+ }
6736
+ // <Card title="x">…</Card> → Card({ title: "x", children: … })
6737
+ const call = { kind: 'CallExpression', expression: tag, arguments: [], parent: node.parent };
6738
+ tag.parent = call;
6739
+ const properties = [];
6740
+ for (const a of attributes) {
6741
+ const name = { kind: 'Identifier', text: a.name.text };
6742
+ const property = { kind: 'PropertyAssignment', name, initializer: getJsxAttributeValue(a) };
6743
+ property.initializer.parent = property;
6744
+ name.parent = property;
6745
+ properties.push(property);
6746
+ }
6747
+ const meaningful = children.filter((c) => c.kind !== 'JsxText' || cleanJsxText(c.text ?? ''));
6748
+ if (meaningful.length > 0) {
6749
+ const name = { kind: 'Identifier', text: 'children' };
6750
+ const fragment = { kind: 'JsxFragment', children };
6751
+ const property = { kind: 'PropertyAssignment', name, initializer: fragment };
6752
+ fragment.parent = property;
6753
+ name.parent = property;
6754
+ properties.push(property);
6755
+ }
6756
+ const props = { kind: 'ObjectLiteralExpression', properties, parent: call };
6757
+ for (const property of properties)
6758
+ property.parent = props;
6759
+ const declared = isIdentifier(tag) ? declaredFunctions.get(tag.text) : undefined;
6760
+ if (properties.length > 0 || (declared?.parameters ?? []).length > 0) {
6761
+ const args = [props];
6762
+ call.arguments = args;
6763
+ }
6764
+ return visit(call);
6765
+ }
6766
+ function getDecorators(node) {
6767
+ return (node.modifiers ?? []).filter((m) => m.kind === 'Decorator');
6768
+ }
6769
+ // `this` inside a function / function expression (not a class member, not an arrow's)
6770
+ function isThisOfPlainFunction(node) {
6771
+ for (let n = node.parent; n; n = n.parent) {
6772
+ if (isArrowFunction(n))
6773
+ continue;
6774
+ if (isFunctionExpression(n) || isFunctionDeclaration(n))
6775
+ return !isPropertyAssignment(n.parent);
6776
+ if (isMethodDeclaration(n) || isConstructorDeclaration(n) || isPropertyDeclaration(n) || isGetAccessor(n) || isSetAccessor(n) || isClassDeclaration(n))
6777
+ return false;
6778
+ }
6779
+ return false;
6780
+ }
6781
+ // f.call(thisArg, a, b) → f(a, b); f.apply(thisArg, args) → f(args...) (this is TnThis)
6782
+ function visitFunctionCallApply(node) {
6783
+ if (!isPropertyAccessExpression(node.expression))
6784
+ return undefined;
6785
+ const method = node.expression.name.text;
6786
+ if (method !== 'call' && method !== 'apply')
6787
+ return undefined;
6788
+ if (!inferExpressionType(node.expression.expression)?.startsWith('func('))
6789
+ return undefined;
6790
+ const fn = visit(node.expression.expression);
6791
+ const args = (node.arguments ?? []).slice(1);
6792
+ if (method === 'apply')
6793
+ return `${fn}(${args[0] ? `${visit(args[0])}...` : ''})`;
6794
+ return `${fn}(${args.map((a) => (isSpreadElement(a) ? `${visit(a.expression)}...` : visit(a))).join(', ')})`;
6795
+ }
6796
+ // Whether a decorator call returns a value (a replacement), or only has effects
6797
+ function decoratorReturnsValue(decorator) {
6798
+ let fnType = inferExpressionType(decorator.expression);
6799
+ // @factory(args): the decorator is what the factory returns
6800
+ const factoryCall = decorator.expression;
6801
+ if (isCallExpression(factoryCall) && isIdentifier(factoryCall.expression)) {
6802
+ const factory = declaredFunctions.get(factoryCall.expression.text);
6803
+ if (factory)
6804
+ fnType = factory.type ? getType(factory.type) : inferFunctionBodyReturnType(factory);
6805
+ }
6806
+ if (fnType?.startsWith('func('))
6807
+ return !!goFuncReturnType(fnType);
6808
+ const call = { kind: 'CallExpression', expression: decorator.expression, arguments: [], parent: decorator };
6809
+ const returnType = inferExpressionType(call);
6810
+ return !!returnType && returnType !== 'struct{}';
6811
+ }
6812
+ // The decorated method m: m__undecorated holds the body; m calls the decorated function
6813
+ // (Owner_m__decorated) with TnThis set to the receiver
6814
+ function emitDecoratedMethod(className, typeParamNames, member, ownerClass) {
6815
+ useHelper('decorators');
6816
+ useHelper('dynamic');
6817
+ const methodName = visit(member.name);
6818
+ const info = getFunctionParametersInfo(member.parameters ?? []);
6819
+ if (info.signature.includes('...'))
6820
+ throw new Error(`decorated method ${methodName}: rest/default parameters are not supported`);
6821
+ const names = splitGoParameters(info.signature).map((p) => p.slice(0, p.indexOf(' ')));
6822
+ const returnType = member.type ? getType(member.type) : '';
6823
+ const result = returnType ? ` ${returnType}` : '';
6824
+ const body = visit(member.body, { prefixBlockContent: info.prefixBlockContent });
6825
+ const call = `${ownerClass}_${methodName}__decorated(${names.join(', ')})`;
6826
+ const invoke = returnType ? `return TnAs[${returnType}](${call})` : call;
6827
+ if ((member.modifiers ?? []).some((m) => m.kind === 'StaticKeyword')) {
6828
+ return `func ${className}_${methodName}__undecorated(${info.signature})${result} ${body}\n\nfunc ${className}_${methodName}(${info.signature})${result} {\n\t\t${invoke}\n\t}\n\n`;
6829
+ }
6830
+ return (`func (self *${className}${typeParamNames}) ${methodName}__undecorated(${info.signature})${result} ${body}\n\n` +
6831
+ `func (self *${className}${typeParamNames}) ${methodName}(${info.signature})${result} {\n\t\t__prevThis := TnThis\n\t\tTnThis = self\n\t\tdefer func() { TnThis = __prevThis }()\n\t\t${invoke}\n\t}\n\n`);
6832
+ }
6833
+ // Applies a class's decorators where it is defined (in source order): each decorated
6834
+ // method's function is wrapped bottom-up, then class decorators run
6835
+ function visitClassDecorators(node) {
6836
+ const className = visit(node.name);
6837
+ const statements = [];
6838
+ for (const member of node.members ?? []) {
6839
+ const decorators = getDecorators(member);
6840
+ if (decorators.length === 0)
6841
+ continue;
6842
+ if (!isMethodDeclaration(member) || !member.body)
6843
+ throw new Error(`${className}: only method and class decorators are supported`);
6844
+ useHelper('decorators');
6845
+ useHelper('dynamic');
6846
+ const methodName = visit(member.name);
6847
+ const isStatic = (member.modifiers ?? []).some((m) => m.kind === 'StaticKeyword');
6848
+ const isPrivate = member.name?.kind === 'PrivateIdentifier';
6849
+ const params = splitGoParameters(getFunctionParametersInfo(member.parameters ?? []).signature);
6850
+ const paramTypes = params.map((p) => p.slice(p.indexOf(' ') + 1));
6851
+ const returnType = member.type ? getType(member.type) : '';
6852
+ const varName = `${className}_${methodName}__decorated`;
6853
+ mainPackageVariables.push(`var ${varName} func(...interface{}) interface{}`);
6854
+ const args = paramTypes.map((t, i) => `TnAs[${t}](__args[${i}])`).join(', ');
6855
+ const target = isStatic
6856
+ ? `${className}_${methodName}__undecorated(${args})`
6857
+ : `TnThis.(interface{ ${methodName}__undecorated(${paramTypes.join(', ')})${returnType ? ` ${returnType}` : ''} }).${methodName}__undecorated(${args})`;
6858
+ statements.push(`${varName} = func(__args ...interface{}) interface{} { ${returnType ? `return ${target}` : `${target}; return nil`} }`);
6859
+ for (const decorator of [...decorators].reverse()) {
6860
+ const context = `&TnDecoratorContext{kind: "method", name: ${toGoStringLiteral(isPrivate ? member.name.text : methodName)}, static: ${isStatic}, private: ${isPrivate}}`;
6861
+ const call = `(${visit(decorator.expression)})(${varName}, ${context})`;
6862
+ statements.push(decoratorReturnsValue(decorator) ? `${varName} = TnDecorate(${varName}, ${call})` : call);
6863
+ }
6864
+ }
6865
+ for (const decorator of [...getDecorators(node)].reverse()) {
6866
+ useHelper('decorators');
6867
+ const context = `&TnDecoratorContext{kind: "class", name: ${toGoStringLiteral(className)}}`;
6868
+ const call = `(${visit(decorator.expression)})(New${className}, ${context})`;
6869
+ statements.push(decoratorReturnsValue(decorator) ? `TnDecorateClass(${call})` : call);
6870
+ }
6871
+ return statements.map((statement) => `${statement};\n\t`).join('');
6872
+ }
6189
6873
  function isStringRawTag(tag) {
6190
6874
  return isPropertyAccessExpression(tag) && isIdentifier(tag.expression) && tag.expression.text === 'String' && tag.name.text === 'raw';
6191
6875
  }
@@ -6361,6 +7045,14 @@ function visitCallArguments(node) {
6361
7045
  return result;
6362
7046
  }
6363
7047
  function withContextualParameters(fn, info) {
7048
+ if (usesArgumentsObject(fn) && !info.signature.includes('...')) {
7049
+ const names = (fn.parameters ?? []).map((p) => getParameterName(p, fn.parameters));
7050
+ registerLocalVariable('arguments', '[]interface{}');
7051
+ return {
7052
+ signature: [info.signature, '__arguments ...interface{}'].filter((part) => part).join(', '),
7053
+ prefixBlockContent: `${info.prefixBlockContent}arguments := append([]interface{}{${names.join(', ')}}, __arguments...)\n\t\t_ = arguments\n\t\t`
7054
+ };
7055
+ }
6364
7056
  const contextualFn = getContextualFunctionType(fn);
6365
7057
  const declaredCount = (fn.parameters ?? []).length;
6366
7058
  const contextualParams = contextualFn?.parameters ?? [];
@@ -6372,6 +7064,16 @@ function withContextualParameters(fn, info) {
6372
7064
  const signature = [info.signature, ...extra].filter((part) => part).join(', ');
6373
7065
  return { ...info, signature };
6374
7066
  }
7067
+ // A non-arrow function whose own body reads `arguments` (arrows see their parent's)
7068
+ function usesArgumentsObject(fn) {
7069
+ if (isArrowFunction(fn) || !fn.body)
7070
+ return false;
7071
+ return containsNode(fn.body, (n) => {
7072
+ if (n !== fn.body && (isFunctionDeclaration(n) || isFunctionExpression(n) || isMethodDeclaration(n)))
7073
+ return false;
7074
+ return isIdentifier(n) && n.text === 'arguments' && !isFieldNameIdentifier(n);
7075
+ });
7076
+ }
6375
7077
  // Destructured parameters ({ a, b }: T / [x, y]: T[]) get a temporary name
6376
7078
  function getParameterName(param, parameters) {
6377
7079
  if (isIdentifier(param.name))
@@ -6612,8 +7314,8 @@ function visitNewSet(node) {
6612
7314
  elementType = getArrayLiteralElementType(node.arguments[0]);
6613
7315
  }
6614
7316
  const args = node.arguments ?? [];
6615
- // new Set(otherSet/values()) — copy at runtime
6616
- if (args.length > 0 && !isArrayLiteralExpression(args[0])) {
7317
+ // new Set(otherSet/values()) / new Set([...a, ...b]) — copy at runtime
7318
+ if (args.length > 0 && (!isArrayLiteralExpression(args[0]) || (args[0].elements ?? []).some((e) => isSpreadElement(e)))) {
6617
7319
  useHelper('orderedMap');
6618
7320
  const src = getTempName('setsrc');
6619
7321
  const item = getTempName('item');
@@ -6648,9 +7350,11 @@ function normalizeCjsToEsm(code) {
6648
7350
  code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
6649
7351
  return code;
6650
7352
  }
6651
- function includeLocalImport(code, dir, goFileName) {
7353
+ function includeLocalImport(code, dir, goFileName, tsx = false) {
6652
7354
  const prevDir = currentFileDir;
7355
+ const prevTsx = currentFileIsTsx;
6653
7356
  currentFileDir = dir;
7357
+ currentFileIsTsx = tsx;
6654
7358
  // Normalize CommonJS modules to ES module syntax before parsing
6655
7359
  if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
6656
7360
  code = normalizeCjsToEsm(code);
@@ -6662,6 +7366,7 @@ function includeLocalImport(code, dir, goFileName) {
6662
7366
  visit(stmt, { addFunctionOutside: true });
6663
7367
  }
6664
7368
  currentFileDir = prevDir;
7369
+ currentFileIsTsx = prevTsx;
6665
7370
  return;
6666
7371
  }
6667
7372
  // Separate-file mode (local TS imports): transpile to its own Go file.
@@ -6683,7 +7388,13 @@ function includeLocalImport(code, dir, goFileName) {
6683
7388
  }
6684
7389
  emittingModuleFile = prevEmittingModuleFile;
6685
7390
  const fileOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
6686
- const fileInline = inlineLines.join('\n');
7391
+ const isDeclarationLine = (line) => /^(var|const|type|func|import)\b/.test(line.trim());
7392
+ const declarationLines = inlineLines.filter(isDeclarationLine);
7393
+ const statementLines = inlineLines.filter((line) => !isDeclarationLine(line));
7394
+ const fileInline = [
7395
+ ...declarationLines,
7396
+ ...(statementLines.length > 0 ? [`func init() {\n\t${statementLines.join('\n\t')}\n}`] : [])
7397
+ ].join('\n');
6687
7398
  // Computed after all visits: function bodies (outside nodes) register
6688
7399
  // packages too, and imports must be captured after those visits.
6689
7400
  // Helper-provided packages are only imported here if the file's own code
@@ -6714,6 +7425,7 @@ function includeLocalImport(code, dir, goFileName) {
6714
7425
  importedPackages.clear();
6715
7426
  for (const p of savedPackages)
6716
7427
  importedPackages.add(p);
7428
+ currentFileIsTsx = prevTsx;
6717
7429
  for (const p of helperProvidedPackages)
6718
7430
  importedPackages.add(p);
6719
7431
  currentFileDir = prevDir;
@@ -6778,6 +7490,8 @@ const helperRequires = {
6778
7490
  objectAssign: ['orderedMap'],
6779
7491
  promiseAll: ['orderedMap', 'asyncWait', 'promise'],
6780
7492
  promise: ['error'],
7493
+ decorators: [],
7494
+ jsx: [],
6781
7495
  objectIntegrity: [],
6782
7496
  structuredClone: [],
6783
7497
  globalThis: ['dynamic'],
@@ -6979,6 +7693,8 @@ const helperPackages = {
6979
7693
  uri: ['strings', 'fmt', 'net/url'],
6980
7694
  promiseAll: [],
6981
7695
  promise: ['sync'],
7696
+ decorators: [],
7697
+ jsx: ['strings', 'html', 'reflect', 'strconv', 'fmt'],
6982
7698
  objectIntegrity: ['sync', 'reflect'],
6983
7699
  structuredClone: ['reflect', 'unsafe'],
6984
7700
  globalThis: [],
@@ -7420,14 +8136,30 @@ func TnIsNil(v interface{}) bool {
7420
8136
  return false
7421
8137
  }`,
7422
8138
  typeOf: `func TnTypeOf(v interface{}) string {
7423
- switch v.(type) {
8139
+ // an unset nullable primitive (*T nil) is undefined
8140
+ switch t := v.(type) {
7424
8141
  case nil:
7425
8142
  return "undefined"
7426
- case string, *string:
8143
+ case string:
7427
8144
  return "string"
7428
- case float64, *float64:
8145
+ case *string:
8146
+ if t == nil {
8147
+ return "undefined"
8148
+ }
8149
+ return "string"
8150
+ case float64:
8151
+ return "number"
8152
+ case *float64:
8153
+ if t == nil {
8154
+ return "undefined"
8155
+ }
7429
8156
  return "number"
7430
- case bool, *bool:
8157
+ case bool:
8158
+ return "boolean"
8159
+ case *bool:
8160
+ if t == nil {
8161
+ return "undefined"
8162
+ }
7431
8163
  return "boolean"
7432
8164
  }
7433
8165
  if reflect.ValueOf(v).Kind() == reflect.Func {
@@ -8994,6 +9726,128 @@ func tnCloneValue(v reflect.Value) reflect.Value {
8994
9726
  }`,
8995
9727
  globalThis: `// (globalThis as any).x: properties set on the global object at runtime
8996
9728
  var TnGlobalThis = map[string]interface{}{}`,
9729
+ jsx: `// JSX renders to HTML: TnJsxNode is markup, plain strings are escaped text
9730
+ type TnJsxNode string
9731
+
9732
+ type TnJsxAttr struct {
9733
+ name string
9734
+ value interface{}
9735
+ }
9736
+
9737
+ var tnJsxVoidTags = map[string]bool{"area": true, "base": true, "br": true, "col": true, "embed": true, "hr": true, "img": true, "input": true, "link": true, "meta": true, "source": true, "track": true, "wbr": true}
9738
+
9739
+ func TnJsxElement(tag string, attrs []TnJsxAttr, children []interface{}) TnJsxNode {
9740
+ var b strings.Builder
9741
+ b.WriteString("<" + tag)
9742
+ for _, a := range attrs {
9743
+ name := a.name
9744
+ switch name {
9745
+ case "key", "ref":
9746
+ continue
9747
+ case "className":
9748
+ name = "class"
9749
+ case "htmlFor":
9750
+ name = "for"
9751
+ }
9752
+ if a.value == nil {
9753
+ continue
9754
+ }
9755
+ if v, ok := a.value.(bool); ok {
9756
+ if v {
9757
+ b.WriteString(" " + name)
9758
+ }
9759
+ continue
9760
+ }
9761
+ if reflect.ValueOf(a.value).Kind() == reflect.Func {
9762
+ continue
9763
+ }
9764
+ b.WriteString(" " + name + "=\\"" + html.EscapeString(tnJsxString(a.value)) + "\\"")
9765
+ }
9766
+ if tnJsxVoidTags[tag] {
9767
+ b.WriteString("/>")
9768
+ return TnJsxNode(b.String())
9769
+ }
9770
+ b.WriteString(">")
9771
+ b.WriteString(string(TnJsxChildren(children)))
9772
+ b.WriteString("</" + tag + ">")
9773
+ return TnJsxNode(b.String())
9774
+ }
9775
+
9776
+ func TnJsxChildren(children []interface{}) TnJsxNode {
9777
+ var b strings.Builder
9778
+ for _, c := range children {
9779
+ tnJsxWriteChild(&b, c)
9780
+ }
9781
+ return TnJsxNode(b.String())
9782
+ }
9783
+
9784
+ // null / booleans render nothing; arrays render each item
9785
+ func tnJsxWriteChild(b *strings.Builder, c interface{}) {
9786
+ switch v := c.(type) {
9787
+ case nil, bool:
9788
+ return
9789
+ case TnJsxNode:
9790
+ b.WriteString(string(v))
9791
+ return
9792
+ case string:
9793
+ b.WriteString(html.EscapeString(v))
9794
+ return
9795
+ }
9796
+ rv := reflect.ValueOf(c)
9797
+ switch rv.Kind() {
9798
+ case reflect.Slice:
9799
+ for i := 0; i < rv.Len(); i++ {
9800
+ tnJsxWriteChild(b, rv.Index(i).Interface())
9801
+ }
9802
+ return
9803
+ case reflect.Pointer:
9804
+ if !rv.IsNil() {
9805
+ tnJsxWriteChild(b, rv.Elem().Interface())
9806
+ }
9807
+ return
9808
+ }
9809
+ b.WriteString(html.EscapeString(tnJsxString(c)))
9810
+ }
9811
+
9812
+ func tnJsxString(v interface{}) string {
9813
+ switch t := v.(type) {
9814
+ case string:
9815
+ return t
9816
+ case TnJsxNode:
9817
+ return string(t)
9818
+ case float64:
9819
+ return strconv.FormatFloat(t, 'f', -1, 64)
9820
+ }
9821
+ return fmt.Sprint(v)
9822
+ }`,
9823
+ decorators: `// this inside plain functions: the receiver of the decorated method being called
9824
+ var TnThis interface{}
9825
+
9826
+ // The context a decorator receives (kind is "method" or "class")
9827
+ type TnDecoratorContext struct {
9828
+ kind string
9829
+ name string
9830
+ static bool
9831
+ private bool
9832
+ }
9833
+
9834
+ // A method decorator's result replaces the method; undefined keeps it
9835
+ func TnDecorate(prev func(...interface{}) interface{}, result interface{}) func(...interface{}) interface{} {
9836
+ if result == nil {
9837
+ return prev
9838
+ }
9839
+ if f, ok := result.(func(...interface{}) interface{}); ok && f != nil {
9840
+ return f
9841
+ }
9842
+ return prev
9843
+ }
9844
+
9845
+ // Class decorators may not replace a class (a Go type)
9846
+ func TnDecorateClass(result interface{}) {
9847
+ if result != nil {
9848
+ panic("class decorators cannot replace the class")
9849
+ }
9850
+ }`,
8997
9851
  promise: `// Promises are channels; a rejected promise is a closed channel with a reason
8998
9852
  var tnRejections sync.Map
8999
9853
 
@@ -9347,11 +10201,13 @@ function visitImportDeclaration(node) {
9347
10201
  const resolved = fileResolver(moduleSpec, currentFileDir);
9348
10202
  if (resolved) {
9349
10203
  const goFileName = specifierToGoFileName(moduleSpec);
9350
- includeLocalImport(resolved.content, resolved.dir, goFileName);
10204
+ includeLocalImport(resolved.content, resolved.dir, goFileName, !!resolved.tsx);
9351
10205
  }
9352
10206
  }
9353
10207
  // Renamed bindings refer to the original declaration (one Go package)
9354
10208
  const bindings = node.importClause?.namedBindings;
10209
+ if (bindings && isNamespaceImport(bindings))
10210
+ defaultImportNamespaces.add(bindings.name.text);
9355
10211
  if (bindings && isNamedImports(bindings)) {
9356
10212
  for (const el of bindings.elements ?? []) {
9357
10213
  if (!el.propertyName)
@@ -9407,7 +10263,8 @@ function visitImportDeclaration(node) {
9407
10263
  // variables (a variable named `big` would shadow the package name)
9408
10264
  const goImportAliases = {
9409
10265
  'math/big': 'tnbig',
9410
- 'net/url': 'tnurl'
10266
+ 'net/url': 'tnurl',
10267
+ 'golang.org/x/text/unicode/norm': 'tnnorm'
9411
10268
  };
9412
10269
  function goImportLine(pkg) {
9413
10270
  const alias = goImportAliases[pkg];