python2ts 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -214,7 +214,48 @@ var PYTHON_TO_JS_NAMES = {
214
214
  // core module
215
215
  // ============================================================================
216
216
  floordiv: "floorDiv",
217
- divmod: "divMod"
217
+ divmod: "divMod",
218
+ // ============================================================================
219
+ // hashlib module
220
+ // ============================================================================
221
+ pbkdf2_hmac: "pbkdf2Hmac",
222
+ compare_digest: "compareDigest",
223
+ file_digest: "fileDigest",
224
+ new: "newHash",
225
+ // ============================================================================
226
+ // pathlib module (Path methods)
227
+ // ============================================================================
228
+ read_text: "readText",
229
+ write_text: "writeText",
230
+ read_bytes: "readBytes",
231
+ write_bytes: "writeBytes",
232
+ is_file: "isFile",
233
+ is_dir: "isDir",
234
+ is_symlink: "isSymlink",
235
+ symlink_to: "symlinkTo",
236
+ link_to: "linkTo",
237
+ // ============================================================================
238
+ // shutil module
239
+ // ============================================================================
240
+ copy2: "copy2",
241
+ copytree: "copytree",
242
+ rmtree: "rmtree",
243
+ disk_usage: "diskUsage",
244
+ copymode: "copyMode",
245
+ copystat: "copyStat",
246
+ copyfile: "copyFile",
247
+ get_terminal_size: "getTerminalSize",
248
+ // ============================================================================
249
+ // tempfile module
250
+ // ============================================================================
251
+ mkstemp: "mkstemp",
252
+ mkdtemp: "mkdtemp",
253
+ gettempdir: "getTempDir",
254
+ // ============================================================================
255
+ // glob module
256
+ // ============================================================================
257
+ iglob: "iglob",
258
+ rglob: "rglob"
218
259
  };
219
260
  function toJsName(pythonName) {
220
261
  return PYTHON_TO_JS_NAMES[pythonName] ?? pythonName;
@@ -253,6 +294,23 @@ function declareVariable(ctx, name) {
253
294
  currentScope.add(name);
254
295
  }
255
296
  }
297
+ function stripOuterParens(code) {
298
+ const trimmed = code.trim();
299
+ if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
300
+ const inner = trimmed.slice(1, -1);
301
+ if (/(?<![!=<>])=(?!=)/.test(inner)) {
302
+ return code;
303
+ }
304
+ let depth = 0;
305
+ for (let i = 0; i < trimmed.length; i++) {
306
+ if (trimmed[i] === "(") depth++;
307
+ else if (trimmed[i] === ")") depth--;
308
+ if (depth === 0 && i < trimmed.length - 1) return code;
309
+ }
310
+ return inner;
311
+ }
312
+ return code;
313
+ }
256
314
  function containsYield(node) {
257
315
  if (node.name === "YieldStatement" || node.name === "YieldExpression") {
258
316
  return true;
@@ -395,7 +453,7 @@ function transformPythonType(node, ctx) {
395
453
  }
396
454
  }
397
455
  function extractTypeAnnotation(typeDef, ctx) {
398
- if (!typeDef || typeDef.name !== "TypeDef") return null;
456
+ if (typeDef?.name !== "TypeDef") return null;
399
457
  const children = getChildren(typeDef);
400
458
  const typeNode = children.find((c) => c.name !== ":" && c.name !== "->");
401
459
  if (typeNode) {
@@ -405,7 +463,7 @@ function extractTypeAnnotation(typeDef, ctx) {
405
463
  }
406
464
  function extractTypeModifiers(typeDef, ctx) {
407
465
  const result = { isFinal: false, isClassVar: false };
408
- if (!typeDef || typeDef.name !== "TypeDef") return result;
466
+ if (typeDef?.name !== "TypeDef") return result;
409
467
  const children = getChildren(typeDef);
410
468
  const typeNode = children.find((c) => c.name !== ":" && c.name !== "->");
411
469
  if (!typeNode) return result;
@@ -461,7 +519,7 @@ function isDocstringNode(node, ctx) {
461
519
  if (node.name !== "ExpressionStatement") return false;
462
520
  const children = getChildren(node);
463
521
  const firstChild = children[0];
464
- if (!firstChild || firstChild.name !== "String") return false;
522
+ if (firstChild?.name !== "String") return false;
465
523
  const text = getNodeText(firstChild, ctx.source);
466
524
  return text.startsWith('"""') || text.startsWith("'''");
467
525
  }
@@ -544,7 +602,7 @@ function parseDocstring(content) {
544
602
  descriptionLines.push(trimmed);
545
603
  break;
546
604
  case "params": {
547
- const googleMatch = trimmed.match(/^(\w+)\s*(?:\([^)]*\))?\s*:\s*(.*)$/);
605
+ const googleMatch = /^(\w+)\s*(?:\([^)]*\))?\s*:\s*(.*)$/.exec(trimmed);
548
606
  if (googleMatch) {
549
607
  flushParam();
550
608
  currentParamName = googleMatch[1] ?? "";
@@ -572,7 +630,7 @@ function parseDocstring(content) {
572
630
  break;
573
631
  }
574
632
  case "throws": {
575
- const throwsMatch = trimmed.match(/^(\w+)\s*:\s*(.*)$/);
633
+ const throwsMatch = /^(\w+)\s*:\s*(.*)$/.exec(trimmed);
576
634
  if (throwsMatch) {
577
635
  flushThrows();
578
636
  currentThrowsType = throwsMatch[1] ?? "Error";
@@ -1026,7 +1084,7 @@ function isChainedComparison(node) {
1026
1084
  if (node.name !== "BinaryExpression") return false;
1027
1085
  const children = getChildren(node);
1028
1086
  const op = children[1];
1029
- if (!op || op.name !== "CompareOp") return false;
1087
+ if (op?.name !== "CompareOp") return false;
1030
1088
  return true;
1031
1089
  }
1032
1090
  function extractRightOperand(node, ctx) {
@@ -1076,7 +1134,7 @@ function transformConditionalExpression(node, ctx) {
1076
1134
  const condition = exprs[1];
1077
1135
  const falseExpr = exprs[2];
1078
1136
  if (trueExpr && condition && falseExpr) {
1079
- const condCode = transformNode(condition, ctx);
1137
+ const condCode = stripOuterParens(transformNode(condition, ctx));
1080
1138
  const trueCode = transformNode(trueExpr, ctx);
1081
1139
  const falseCode = transformNode(falseExpr, ctx);
1082
1140
  return `(${condCode} ? ${trueCode} : ${falseCode})`;
@@ -1418,6 +1476,52 @@ function transformCallExpression(node, ctx) {
1418
1476
  case "capwords":
1419
1477
  ctx.usesRuntime.add("string/capWords");
1420
1478
  return `capWords(${args})`;
1479
+ // glob module - async functions (direct import)
1480
+ case "glob":
1481
+ ctx.usesRuntime.add("glob/glob");
1482
+ return `await glob(${args})`;
1483
+ case "iglob":
1484
+ ctx.usesRuntime.add("glob/iglob");
1485
+ return `await iglob(${args})`;
1486
+ case "rglob":
1487
+ ctx.usesRuntime.add("glob/rglob");
1488
+ return `await rglob(${args})`;
1489
+ // shutil module - async functions (direct import)
1490
+ case "copy":
1491
+ ctx.usesRuntime.add("shutil/copy");
1492
+ return `await copy(${args})`;
1493
+ case "copy2":
1494
+ ctx.usesRuntime.add("shutil/copy2");
1495
+ return `await copy2(${args})`;
1496
+ case "copytree":
1497
+ ctx.usesRuntime.add("shutil/copytree");
1498
+ return `await copytree(${args})`;
1499
+ case "move":
1500
+ ctx.usesRuntime.add("shutil/move");
1501
+ return `await move(${args})`;
1502
+ case "rmtree":
1503
+ ctx.usesRuntime.add("shutil/rmtree");
1504
+ return `await rmtree(${args})`;
1505
+ case "which":
1506
+ ctx.usesRuntime.add("shutil/which");
1507
+ return `await which(${args})`;
1508
+ // tempfile module - async functions (direct import)
1509
+ case "mkstemp":
1510
+ ctx.usesRuntime.add("tempfile/mkstemp");
1511
+ return `await mkstemp(${args})`;
1512
+ case "mkdtemp":
1513
+ ctx.usesRuntime.add("tempfile/mkdtemp");
1514
+ return `await mkdtemp(${args})`;
1515
+ case "NamedTemporaryFile":
1516
+ ctx.usesRuntime.add("tempfile/NamedTemporaryFile");
1517
+ return `await NamedTemporaryFile.create(${args})`;
1518
+ case "TemporaryDirectory":
1519
+ ctx.usesRuntime.add("tempfile/TemporaryDirectory");
1520
+ return `await TemporaryDirectory.create(${args})`;
1521
+ // pathlib module
1522
+ case "Path":
1523
+ ctx.usesRuntime.add("pathlib/Path");
1524
+ return `new Path(${args})`;
1421
1525
  /* v8 ignore next 3 -- pass-through for user-defined functions @preserve */
1422
1526
  default:
1423
1527
  return `${transformNode(callee, ctx)}(${args})`;
@@ -1436,9 +1540,10 @@ function transformModuleCall(calleeName, args, ctx) {
1436
1540
  inf: "inf",
1437
1541
  nan: "nan"
1438
1542
  };
1439
- if (funcName in mathConstants) {
1543
+ const mathConstant = mathConstants[funcName];
1544
+ if (mathConstant !== void 0) {
1440
1545
  ctx.usesRuntime.add(`math/${funcName}`);
1441
- return mathConstants[funcName];
1546
+ return mathConstant;
1442
1547
  }
1443
1548
  ctx.usesRuntime.add(`math/${funcName}`);
1444
1549
  return `${funcName}(${args})`;
@@ -1453,14 +1558,45 @@ function transformModuleCall(calleeName, args, ctx) {
1453
1558
  return `${funcName}(${args})`;
1454
1559
  }
1455
1560
  if (moduleName === "os") {
1561
+ const asyncPathFuncs = [
1562
+ "exists",
1563
+ "isfile",
1564
+ "isdir",
1565
+ "islink",
1566
+ "realpath",
1567
+ "getsize",
1568
+ "getmtime",
1569
+ "getatime",
1570
+ "getctime"
1571
+ ];
1572
+ const asyncOsFuncs = [
1573
+ "listdir",
1574
+ "mkdir",
1575
+ "makedirs",
1576
+ "remove",
1577
+ "unlink",
1578
+ "rmdir",
1579
+ "removedirs",
1580
+ "rename",
1581
+ "renames",
1582
+ "replace",
1583
+ "walk",
1584
+ "stat"
1585
+ ];
1456
1586
  if (funcName.startsWith("path.")) {
1457
1587
  const pathFuncName = funcName.slice(5);
1458
1588
  const jsPathFunc = toJsName(pathFuncName);
1459
1589
  ctx.usesRuntime.add("os/path");
1590
+ if (asyncPathFuncs.includes(pathFuncName.toLowerCase())) {
1591
+ return `await path.${jsPathFunc}(${args})`;
1592
+ }
1460
1593
  return `path.${jsPathFunc}(${args})`;
1461
1594
  }
1462
1595
  const jsName = toJsName(funcName);
1463
1596
  ctx.usesRuntime.add(`os/${jsName}`);
1597
+ if (asyncOsFuncs.includes(funcName.toLowerCase())) {
1598
+ return `await ${jsName}(${args})`;
1599
+ }
1464
1600
  return `${jsName}(${args})`;
1465
1601
  }
1466
1602
  if (moduleName === "datetime") {
@@ -1501,6 +1637,67 @@ function transformModuleCall(calleeName, args, ctx) {
1501
1637
  }
1502
1638
  return `${funcName}(${args})`;
1503
1639
  }
1640
+ if (moduleName === "hashlib") {
1641
+ const jsName = toJsName(funcName);
1642
+ ctx.usesRuntime.add(`hashlib/${jsName}`);
1643
+ if (["pbkdf2_hmac", "scrypt", "compare_digest", "file_digest"].includes(funcName)) {
1644
+ const asyncJsName = toJsName(funcName);
1645
+ return `await ${asyncJsName}(${args})`;
1646
+ }
1647
+ return `${jsName}(${args})`;
1648
+ }
1649
+ if (moduleName === "shutil") {
1650
+ const asyncShutilFuncs = [
1651
+ "copy",
1652
+ "copy2",
1653
+ "copytree",
1654
+ "move",
1655
+ "rmtree",
1656
+ "which",
1657
+ "disk_usage",
1658
+ "copymode",
1659
+ "copystat",
1660
+ "copyfile"
1661
+ ];
1662
+ const jsName = toJsName(funcName);
1663
+ ctx.usesRuntime.add(`shutil/${jsName}`);
1664
+ if (asyncShutilFuncs.includes(funcName.toLowerCase())) {
1665
+ return `await ${jsName}(${args})`;
1666
+ }
1667
+ return `${jsName}(${args})`;
1668
+ }
1669
+ if (moduleName === "glob") {
1670
+ const asyncGlobFuncs = ["glob", "iglob", "rglob"];
1671
+ const jsName = toJsName(funcName);
1672
+ ctx.usesRuntime.add(`glob/${jsName}`);
1673
+ if (asyncGlobFuncs.includes(funcName.toLowerCase())) {
1674
+ return `await ${jsName}(${args})`;
1675
+ }
1676
+ return `${jsName}(${args})`;
1677
+ }
1678
+ if (moduleName === "tempfile") {
1679
+ const asyncTempfileFuncs = ["mkstemp", "mkdtemp"];
1680
+ const jsName = toJsName(funcName);
1681
+ ctx.usesRuntime.add(`tempfile/${jsName}`);
1682
+ if (funcName === "NamedTemporaryFile") {
1683
+ return `await NamedTemporaryFile.create(${args})`;
1684
+ }
1685
+ if (funcName === "TemporaryDirectory") {
1686
+ return `await TemporaryDirectory.create(${args})`;
1687
+ }
1688
+ if (asyncTempfileFuncs.includes(funcName.toLowerCase())) {
1689
+ return `await ${jsName}(${args})`;
1690
+ }
1691
+ return `${jsName}(${args})`;
1692
+ }
1693
+ if (moduleName === "pathlib") {
1694
+ const jsName = toJsName(funcName);
1695
+ ctx.usesRuntime.add(`pathlib/${jsName}`);
1696
+ if (funcName === "Path") {
1697
+ return `new Path(${args})`;
1698
+ }
1699
+ return `${jsName}(${args})`;
1700
+ }
1504
1701
  return null;
1505
1702
  }
1506
1703
  function transformMethodCall(callee, args, ctx) {
@@ -1512,6 +1709,29 @@ function transformMethodCall(callee, args, ctx) {
1512
1709
  if (obj.name === "MemberExpression") {
1513
1710
  return null;
1514
1711
  }
1712
+ if (obj.name === "VariableName") {
1713
+ const objName = getNodeText(obj, ctx.source);
1714
+ const knownModules = [
1715
+ "shutil",
1716
+ "glob",
1717
+ "tempfile",
1718
+ "pathlib",
1719
+ "os",
1720
+ "math",
1721
+ "random",
1722
+ "json",
1723
+ "datetime",
1724
+ "re",
1725
+ "string",
1726
+ "functools",
1727
+ "itertools",
1728
+ "collections",
1729
+ "hashlib"
1730
+ ];
1731
+ if (knownModules.includes(objName)) {
1732
+ return null;
1733
+ }
1734
+ }
1515
1735
  const objCode = transformNode(obj, ctx);
1516
1736
  const methodName = getNodeText(methodNode, ctx.source);
1517
1737
  switch (methodName) {
@@ -1670,6 +1890,26 @@ function transformMethodCall(callee, args, ctx) {
1670
1890
  case "issuperset":
1671
1891
  ctx.usesRuntime.add("set");
1672
1892
  return `set.issuperset(${objCode}, ${args})`;
1893
+ // Hash object methods (hashlib) - async
1894
+ case "digest":
1895
+ return `await ${objCode}.digest()`;
1896
+ case "hexdigest":
1897
+ return `await ${objCode}.hexdigest()`;
1898
+ // Path object methods (pathlib) - async
1899
+ // Only handle snake_case methods that are unique to Path
1900
+ case "is_file":
1901
+ case "is_dir":
1902
+ case "is_symlink":
1903
+ case "read_text":
1904
+ case "write_text":
1905
+ case "read_bytes":
1906
+ case "write_bytes":
1907
+ case "symlink_to":
1908
+ case "link_to":
1909
+ case "iterdir": {
1910
+ const jsMethod = toJsName(methodName);
1911
+ return `await ${objCode}.${jsMethod}(${args})`;
1912
+ }
1673
1913
  /* v8 ignore next 2 -- unknown method, let caller handle @preserve */
1674
1914
  default:
1675
1915
  return null;
@@ -1717,7 +1957,7 @@ function transformArgList(node, ctx) {
1717
1957
  }
1718
1958
  if (item.name === "VariableName") {
1719
1959
  const nextItem = items[i + 1];
1720
- if (nextItem && nextItem.name === "AssignOp") {
1960
+ if (nextItem?.name === "AssignOp") {
1721
1961
  const valueItem = items[i + 2];
1722
1962
  if (valueItem) {
1723
1963
  const name = getNodeText(item, ctx.source);
@@ -1931,7 +2171,7 @@ function transformIfStatement(node, ctx) {
1931
2171
  const condition = children[i + 1];
1932
2172
  const body = children.find((c, idx) => idx > i && c.name === "Body");
1933
2173
  if (condition && body) {
1934
- const condCode = transformNode(condition, ctx);
2174
+ const condCode = stripOuterParens(transformNode(condition, ctx));
1935
2175
  const bodyCode = transformBody(body, ctx);
1936
2176
  parts.push(`if (${condCode}) {
1937
2177
  ${bodyCode}
@@ -1941,7 +2181,7 @@ ${bodyCode}
1941
2181
  const condition = children[i + 1];
1942
2182
  const body = children.find((c, idx) => idx > i + 1 && c.name === "Body");
1943
2183
  if (condition && body) {
1944
- const condCode = transformNode(condition, ctx);
2184
+ const condCode = stripOuterParens(transformNode(condition, ctx));
1945
2185
  const bodyCode = transformBody(body, ctx);
1946
2186
  parts.push(` else if (${condCode}) {
1947
2187
  ${bodyCode}
@@ -1967,7 +2207,7 @@ function transformWhileStatement(node, ctx) {
1967
2207
  );
1968
2208
  const body = children.find((c) => c.name === "Body");
1969
2209
  if (!condition || !body) return getNodeText(node, ctx.source);
1970
- const condCode = transformNode(condition, ctx);
2210
+ const condCode = stripOuterParens(transformNode(condition, ctx));
1971
2211
  const bodyCode = transformBody(body, ctx);
1972
2212
  return `while (${condCode}) {
1973
2213
  ${bodyCode}
@@ -1981,8 +2221,8 @@ function transformMatchStatement(node, ctx) {
1981
2221
  if (child.name === "match" || child.name === ":") continue;
1982
2222
  if (child.name === "MatchBody") {
1983
2223
  matchBody = child;
1984
- } else if (!subject) {
1985
- subject = child;
2224
+ } else {
2225
+ subject ??= child;
1986
2226
  }
1987
2227
  }
1988
2228
  if (!subject || !matchBody) return getNodeText(node, ctx.source);
@@ -2032,8 +2272,8 @@ function transformMatchAsIfElse(subjectCode, clauses, ctx) {
2032
2272
  body = child;
2033
2273
  } else if (child.name === "Guard") {
2034
2274
  guard = child;
2035
- } else if (!pattern) {
2036
- pattern = child;
2275
+ } else {
2276
+ pattern ??= child;
2037
2277
  }
2038
2278
  }
2039
2279
  if (!pattern || !body) continue;
@@ -2259,8 +2499,8 @@ function transformMatchClause(node, ctx, indent) {
2259
2499
  if (child.name === "case" || child.name === ":") continue;
2260
2500
  if (child.name === "Body") {
2261
2501
  body = child;
2262
- } else if (!pattern) {
2263
- pattern = child;
2502
+ } else {
2503
+ pattern ??= child;
2264
2504
  }
2265
2505
  }
2266
2506
  if (!pattern || !body) return "";
@@ -2380,7 +2620,7 @@ function transformTryStatement(node, ctx) {
2380
2620
  }
2381
2621
  if (child.name === "try") {
2382
2622
  const nextBody = children[i + 1];
2383
- if (nextBody && nextBody.name === "Body") {
2623
+ if (nextBody?.name === "Body") {
2384
2624
  tryBody = nextBody;
2385
2625
  i += 2;
2386
2626
  continue;
@@ -2418,7 +2658,7 @@ function transformTryStatement(node, ctx) {
2418
2658
  }
2419
2659
  if (child.name === "finally") {
2420
2660
  const nextBody = children[i + 1];
2421
- if (nextBody && nextBody.name === "Body") {
2661
+ if (nextBody?.name === "Body") {
2422
2662
  finallyBody = nextBody;
2423
2663
  i += 2;
2424
2664
  continue;
@@ -2436,14 +2676,21 @@ ${baseIndent}}`;
2436
2676
  if (exceptBodies.length > 0) {
2437
2677
  const firstExcept = exceptBodies[0];
2438
2678
  if (firstExcept) {
2439
- const catchVar = firstExcept.varName || "e";
2440
- const catchBody = transformBody(firstExcept.body, ctx);
2679
+ const catchVar = firstExcept.varName ?? "e";
2680
+ let catchBody = transformBody(firstExcept.body, ctx);
2681
+ const isEmpty = !catchBody.trim();
2682
+ if (isEmpty) {
2683
+ const innerIndent = " ".repeat(ctx.indentLevel + 1);
2684
+ const exceptionComment = firstExcept.type ? ` - ${firstExcept.type} expected` : "";
2685
+ catchBody = `${innerIndent}// Intentionally empty${exceptionComment}`;
2686
+ }
2687
+ const catchClause = isEmpty && !firstExcept.varName ? "catch" : `catch (${catchVar})`;
2441
2688
  if (exceptBodies.length === 1 && !firstExcept.type) {
2442
- result += ` catch (${catchVar}) {
2689
+ result += ` ${catchClause} {
2443
2690
  ${catchBody}
2444
2691
  ${baseIndent}}`;
2445
2692
  } else if (exceptBodies.length === 1) {
2446
- result += ` catch (${catchVar}) {
2693
+ result += ` ${catchClause} {
2447
2694
  ${catchBody}
2448
2695
  ${baseIndent}}`;
2449
2696
  } else {
@@ -2453,7 +2700,7 @@ ${baseIndent}}`;
2453
2700
  const exc = exceptBodies[idx];
2454
2701
  if (!exc) continue;
2455
2702
  const excBodyCode = transformBody(exc.body, ctx);
2456
- const excVar = exc.varName || catchVar;
2703
+ const excVar = exc.varName ?? catchVar;
2457
2704
  if (exc.type) {
2458
2705
  const condition = idx === 0 ? "if" : "} else if";
2459
2706
  const mappedType = mapExceptionType(exc.type);
@@ -2508,7 +2755,7 @@ function mapExceptionType(pythonType) {
2508
2755
  NameError: "ReferenceError",
2509
2756
  SyntaxError: "SyntaxError"
2510
2757
  };
2511
- return mapping[pythonType] || "Error";
2758
+ return mapping[pythonType] ?? "Error";
2512
2759
  }
2513
2760
  function transformRaiseStatement(node, ctx) {
2514
2761
  const children = getChildren(node);
@@ -2598,9 +2845,9 @@ function transformSimpleImport(children, ctx) {
2598
2845
  const moduleName = getNodeText(child, ctx.source);
2599
2846
  let alias = null;
2600
2847
  const nextChild = children[i + 1];
2601
- if (nextChild && nextChild.name === "as") {
2848
+ if (nextChild?.name === "as") {
2602
2849
  const aliasChild = children[i + 2];
2603
- if (aliasChild && aliasChild.name === "VariableName") {
2850
+ if (aliasChild?.name === "VariableName") {
2604
2851
  alias = getNodeText(aliasChild, ctx.source);
2605
2852
  i += 3;
2606
2853
  names.push({ module: moduleName, alias });
@@ -2620,7 +2867,7 @@ function transformSimpleImport(children, ctx) {
2620
2867
  return "";
2621
2868
  }
2622
2869
  return filteredNames.map(({ module, alias }) => {
2623
- const importName = alias || module;
2870
+ const importName = alias ?? module;
2624
2871
  return `import * as ${importName} from "${module}"`;
2625
2872
  }).join("\n");
2626
2873
  }
@@ -2696,9 +2943,9 @@ function transformFromImport(children, ctx) {
2696
2943
  if (child.name === "VariableName") {
2697
2944
  const name = getNodeText(child, ctx.source);
2698
2945
  const nextChild = children[i + 1];
2699
- if (nextChild && nextChild.name === "as") {
2946
+ if (nextChild?.name === "as") {
2700
2947
  const aliasChild = children[i + 2];
2701
- if (aliasChild && aliasChild.name === "VariableName") {
2948
+ if (aliasChild?.name === "VariableName") {
2702
2949
  imports.push({ name, alias: getNodeText(aliasChild, ctx.source) });
2703
2950
  i += 2;
2704
2951
  continue;
@@ -2722,7 +2969,7 @@ function transformFromImport(children, ctx) {
2722
2969
  if (moduleName) {
2723
2970
  modulePath += moduleName;
2724
2971
  } else if (imports.length > 0) {
2725
- modulePath += imports[0]?.name || "";
2972
+ modulePath += imports[0]?.name ?? "";
2726
2973
  }
2727
2974
  } else {
2728
2975
  modulePath = moduleName;
@@ -2733,7 +2980,7 @@ function transformFromImport(children, ctx) {
2733
2980
  if (relativeDots > 0 && !moduleName && imports.length === 1) {
2734
2981
  const imp = imports[0];
2735
2982
  if (imp) {
2736
- const importName = imp.alias || imp.name;
2983
+ const importName = imp.alias ?? imp.name;
2737
2984
  return `import * as ${importName} from "${modulePath}"`;
2738
2985
  }
2739
2986
  }
@@ -2772,9 +3019,9 @@ function transformWithStatement(node, ctx) {
2772
3019
  const expr = child;
2773
3020
  let varName = null;
2774
3021
  const nextChild = children[i + 1];
2775
- if (nextChild && nextChild.name === "as") {
3022
+ if (nextChild?.name === "as") {
2776
3023
  const varChild = children[i + 2];
2777
- if (varChild && varChild.name === "VariableName") {
3024
+ if (varChild?.name === "VariableName") {
2778
3025
  varName = getNodeText(varChild, ctx.source);
2779
3026
  i += 3;
2780
3027
  } else {
@@ -2847,7 +3094,7 @@ function extractParamNames(node, source) {
2847
3094
  }
2848
3095
  if (child.name === "*" || getNodeText(child, source) === "*") {
2849
3096
  const nextChild = children[i + 1];
2850
- if (nextChild && nextChild.name === "VariableName") {
3097
+ if (nextChild?.name === "VariableName") {
2851
3098
  names.push(getNodeText(nextChild, source));
2852
3099
  i += 2;
2853
3100
  continue;
@@ -2857,7 +3104,7 @@ function extractParamNames(node, source) {
2857
3104
  }
2858
3105
  if (child.name === "**" || getNodeText(child, source) === "**") {
2859
3106
  const nextChild = children[i + 1];
2860
- if (nextChild && nextChild.name === "VariableName") {
3107
+ if (nextChild?.name === "VariableName") {
2861
3108
  names.push(getNodeText(nextChild, source));
2862
3109
  i += 2;
2863
3110
  continue;
@@ -3228,7 +3475,7 @@ function transformMethodParamListImpl(node, ctx, includeTypes) {
3228
3475
  isFirstParam = false;
3229
3476
  if (child.name === "*" || getNodeText(child, ctx.source) === "*") {
3230
3477
  const nextChild = children[i + 1];
3231
- if (nextChild && nextChild.name === "VariableName") {
3478
+ if (nextChild?.name === "VariableName") {
3232
3479
  const name = getNodeText(nextChild, ctx.source);
3233
3480
  params.push(`...${name}`);
3234
3481
  i += 2;
@@ -3239,7 +3486,7 @@ function transformMethodParamListImpl(node, ctx, includeTypes) {
3239
3486
  }
3240
3487
  if (child.name === "**" || getNodeText(child, ctx.source) === "**") {
3241
3488
  const nextChild = children[i + 1];
3242
- if (nextChild && nextChild.name === "VariableName") {
3489
+ if (nextChild?.name === "VariableName") {
3243
3490
  const name = getNodeText(nextChild, ctx.source);
3244
3491
  params.push(name);
3245
3492
  i += 2;
@@ -3582,7 +3829,7 @@ function parseDataclassFieldFromExpression(node, ctx) {
3582
3829
  }
3583
3830
  function parseFieldDefaultFactory(callNode, ctx) {
3584
3831
  const text = getNodeText(callNode, ctx.source);
3585
- const factoryMatch = text.match(/default_factory\s*=\s*(\w+)/);
3832
+ const factoryMatch = /default_factory\s*=\s*(\w+)/.exec(text);
3586
3833
  if (factoryMatch) {
3587
3834
  const factory = factoryMatch[1];
3588
3835
  if (factory === "list") return "[]";
@@ -3590,7 +3837,7 @@ function parseFieldDefaultFactory(callNode, ctx) {
3590
3837
  if (factory === "set") return "new Set()";
3591
3838
  if (factory) return `${factory}()`;
3592
3839
  }
3593
- const defaultMatch = text.match(/default\s*=\s*([^,)]+)/);
3840
+ const defaultMatch = /default\s*=\s*([^,)]+)/.exec(text);
3594
3841
  if (defaultMatch) {
3595
3842
  return defaultMatch[1]?.trim() ?? "undefined";
3596
3843
  }
@@ -3809,9 +4056,8 @@ function parseEnumMember(node, ctx) {
3809
4056
  function checkSequentialEnum(members) {
3810
4057
  if (members.length === 0) return true;
3811
4058
  if (members.some((m) => m.value === "auto")) return true;
3812
- const numericMembers = members.filter((m) => m.numericValue !== null);
3813
- if (numericMembers.length !== members.length) return false;
3814
- const values = numericMembers.map((m) => m.numericValue);
4059
+ const values = members.map((m) => m.numericValue).filter((v) => v !== null);
4060
+ if (values.length !== members.length) return false;
3815
4061
  const firstValue = values[0];
3816
4062
  if (firstValue === void 0) return false;
3817
4063
  for (let i = 1; i < values.length; i++) {
@@ -3893,7 +4139,7 @@ function transformProtocol(className, parentClasses, body, ctx) {
3893
4139
  members.push(`${memberIndent}${sig}`);
3894
4140
  }
3895
4141
  } else if (child.name === "ExpressionStatement" || child.name === "AssignStatement") {
3896
- const field = parseDataclassFieldFromExpression(child, ctx) || parseDataclassFieldFromAssignment(child, ctx);
4142
+ const field = parseDataclassFieldFromExpression(child, ctx) ?? parseDataclassFieldFromAssignment(child, ctx);
3897
4143
  if (field) {
3898
4144
  members.push(`${memberIndent}${field.name}: ${field.tsType}`);
3899
4145
  }
@@ -3992,18 +4238,18 @@ function transformParamList(node, ctx) {
3992
4238
  let i = 0;
3993
4239
  const parseParam = (startIndex) => {
3994
4240
  const child = children[startIndex];
3995
- if (!child || child.name !== "VariableName") return null;
4241
+ if (child?.name !== "VariableName") return null;
3996
4242
  const nameCode = getNodeText(child, ctx.source);
3997
4243
  let tsType = null;
3998
4244
  let defaultValue = null;
3999
4245
  let offset = 1;
4000
4246
  const nextChild = children[startIndex + 1];
4001
- if (nextChild && nextChild.name === "TypeDef") {
4247
+ if (nextChild?.name === "TypeDef") {
4002
4248
  tsType = extractTypeAnnotation(nextChild, ctx);
4003
4249
  offset = 2;
4004
4250
  }
4005
4251
  const afterType = children[startIndex + offset];
4006
- if (afterType && afterType.name === "AssignOp") {
4252
+ if (afterType?.name === "AssignOp") {
4007
4253
  const defaultValChild = children[startIndex + offset + 1];
4008
4254
  if (defaultValChild) {
4009
4255
  defaultValue = transformNode(defaultValChild, ctx);
@@ -4034,10 +4280,10 @@ function transformParamList(node, ctx) {
4034
4280
  }
4035
4281
  if (child.name === "*" || getNodeText(child, ctx.source) === "*") {
4036
4282
  const nextChild = children[i + 1];
4037
- if (nextChild && nextChild.name === "VariableName") {
4283
+ if (nextChild?.name === "VariableName") {
4038
4284
  const name = getNodeText(nextChild, ctx.source);
4039
4285
  const typeChild = children[i + 2];
4040
- if (typeChild && typeChild.name === "TypeDef") {
4286
+ if (typeChild?.name === "TypeDef") {
4041
4287
  const tsType = extractTypeAnnotation(typeChild, ctx);
4042
4288
  restParam = tsType ? `...${name}: ${tsType}[]` : `...${name}`;
4043
4289
  i += 3;
@@ -4054,7 +4300,7 @@ function transformParamList(node, ctx) {
4054
4300
  }
4055
4301
  if (child.name === "**" || getNodeText(child, ctx.source) === "**") {
4056
4302
  const nextChild = children[i + 1];
4057
- if (nextChild && nextChild.name === "VariableName") {
4303
+ if (nextChild?.name === "VariableName") {
4058
4304
  kwargsParam = getNodeText(nextChild, ctx.source);
4059
4305
  i += 2;
4060
4306
  continue;
@@ -4511,6 +4757,11 @@ function transformYieldStatement(node, ctx) {
4511
4757
 
4512
4758
  // src/generator/index.ts
4513
4759
  import * as prettier from "prettier";
4760
+ import { ESLint } from "eslint";
4761
+ import tseslint from "typescript-eslint";
4762
+ import { writeFileSync, readFileSync, mkdtempSync, rmSync } from "fs";
4763
+ import { join } from "path";
4764
+ import { tmpdir } from "os";
4514
4765
  var defaultOptions = {
4515
4766
  includeRuntime: true,
4516
4767
  runtimeImportPath: "pythonlib"
@@ -4655,9 +4906,81 @@ function buildRuntimeImports(usedFunctions, basePath) {
4655
4906
  function transpile(python, options = {}) {
4656
4907
  return generate(python, options).code;
4657
4908
  }
4909
+ function createEslintConfig(tempDir) {
4910
+ return {
4911
+ cwd: tempDir,
4912
+ fix: true,
4913
+ overrideConfigFile: true,
4914
+ overrideConfig: [
4915
+ // TypeScript-ESLint strict + stylistic presets (type-checked)
4916
+ ...tseslint.configs.strictTypeChecked,
4917
+ ...tseslint.configs.stylisticTypeChecked,
4918
+ // Custom configuration
4919
+ {
4920
+ files: ["**/*.ts"],
4921
+ languageOptions: {
4922
+ parserOptions: {
4923
+ projectService: true,
4924
+ tsconfigRootDir: tempDir
4925
+ }
4926
+ },
4927
+ rules: {
4928
+ // ESLint core rules (auto-fixable, not in typescript-eslint presets)
4929
+ "prefer-const": "error",
4930
+ "prefer-arrow-callback": "error",
4931
+ "prefer-template": "error",
4932
+ "prefer-rest-params": "error",
4933
+ "prefer-spread": "error",
4934
+ curly: "error",
4935
+ "no-lonely-if": "error",
4936
+ // Disable rules that don't make sense for generated code
4937
+ "@typescript-eslint/explicit-function-return-type": "off",
4938
+ "@typescript-eslint/explicit-module-boundary-types": "off",
4939
+ "@typescript-eslint/no-explicit-any": "off",
4940
+ "@typescript-eslint/no-unsafe-assignment": "off",
4941
+ "@typescript-eslint/no-unsafe-member-access": "off",
4942
+ "@typescript-eslint/no-unsafe-call": "off",
4943
+ "@typescript-eslint/no-unsafe-return": "off",
4944
+ "@typescript-eslint/no-unsafe-argument": "off"
4945
+ }
4946
+ }
4947
+ ]
4948
+ };
4949
+ }
4950
+ async function applyTypedEslintFixes(code) {
4951
+ const tempDir = mkdtempSync(join(tmpdir(), "python2ts-"));
4952
+ try {
4953
+ const tsconfigPath = join(tempDir, "tsconfig.json");
4954
+ writeFileSync(
4955
+ tsconfigPath,
4956
+ JSON.stringify({
4957
+ compilerOptions: {
4958
+ target: "ES2024",
4959
+ module: "ESNext",
4960
+ moduleResolution: "bundler",
4961
+ strict: true,
4962
+ skipLibCheck: true,
4963
+ noEmit: true
4964
+ },
4965
+ include: ["*.ts"]
4966
+ })
4967
+ );
4968
+ const tempFile = join(tempDir, "output.ts");
4969
+ writeFileSync(tempFile, code);
4970
+ const eslint = new ESLint(createEslintConfig(tempDir));
4971
+ const results = await eslint.lintFiles(["output.ts"]);
4972
+ if (results.length > 0 && results[0]?.output) {
4973
+ return results[0].output;
4974
+ }
4975
+ return readFileSync(tempFile, "utf-8");
4976
+ } finally {
4977
+ rmSync(tempDir, { recursive: true, force: true });
4978
+ }
4979
+ }
4658
4980
  async function formatCode(code) {
4659
4981
  try {
4660
- return await prettier.format(code, prettierOptions);
4982
+ const eslintFixed = await applyTypedEslintFixes(code);
4983
+ return await prettier.format(eslintFixed, prettierOptions);
4661
4984
  } catch {
4662
4985
  return code;
4663
4986
  }
@@ -4703,4 +5026,3 @@ export {
4703
5026
  /* v8 ignore next -- bare yield statement @preserve */
4704
5027
  /* v8 ignore next 2 -- fallback for future/unknown runtime functions @preserve */
4705
5028
  /* v8 ignore start -- async wrappers tested via CLI @preserve */
4706
- //# sourceMappingURL=chunk-65ZOMVMA.js.map