python2ts 1.3.2 → 1.4.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.
@@ -262,6 +262,71 @@ function toJsName(pythonName) {
262
262
  }
263
263
 
264
264
  // src/transformer/index.ts
265
+ var JS_RESERVED_KEYWORDS = /* @__PURE__ */ new Set([
266
+ // ECMAScript reserved words
267
+ "break",
268
+ "case",
269
+ "catch",
270
+ "continue",
271
+ "debugger",
272
+ "default",
273
+ "delete",
274
+ "do",
275
+ "else",
276
+ "finally",
277
+ "for",
278
+ "function",
279
+ "if",
280
+ "in",
281
+ "instanceof",
282
+ "new",
283
+ "return",
284
+ "switch",
285
+ "this",
286
+ "throw",
287
+ "try",
288
+ "typeof",
289
+ "var",
290
+ "void",
291
+ "while",
292
+ "with",
293
+ // ECMAScript 6+ reserved words
294
+ "class",
295
+ "const",
296
+ "enum",
297
+ "export",
298
+ "extends",
299
+ "import",
300
+ // Note: 'super' is intentionally NOT in this list as it's valid in JS class contexts
301
+ // Strict mode reserved words
302
+ "implements",
303
+ "interface",
304
+ "let",
305
+ "package",
306
+ "private",
307
+ "protected",
308
+ "public",
309
+ "static",
310
+ "yield",
311
+ // TypeScript reserved words
312
+ "abstract",
313
+ "as",
314
+ "async",
315
+ "await",
316
+ "declare",
317
+ "from",
318
+ "get",
319
+ "is",
320
+ "module",
321
+ "namespace",
322
+ "of",
323
+ "require",
324
+ "set",
325
+ "type"
326
+ ]);
327
+ function escapeReservedKeyword(name) {
328
+ return JS_RESERVED_KEYWORDS.has(name) ? `_${name}` : name;
329
+ }
265
330
  function createContext(source) {
266
331
  return {
267
332
  source,
@@ -521,13 +586,23 @@ function isDocstringNode(node, ctx) {
521
586
  const firstChild = children[0];
522
587
  if (firstChild?.name !== "String") return false;
523
588
  const text = getNodeText(firstChild, ctx.source);
524
- return text.startsWith('"""') || text.startsWith("'''");
589
+ return isTripleQuotedString(text);
590
+ }
591
+ function isTripleQuotedString(text) {
592
+ let stripped = text;
593
+ if (/^[rRuU]/.test(text)) {
594
+ stripped = text.slice(1);
595
+ }
596
+ return stripped.startsWith('"""') || stripped.startsWith("'''");
525
597
  }
526
598
  function extractDocstringContent(node, ctx) {
527
599
  const children = getChildren(node);
528
600
  const stringNode = children[0];
529
601
  if (!stringNode) return "";
530
- const text = getNodeText(stringNode, ctx.source);
602
+ let text = getNodeText(stringNode, ctx.source);
603
+ if (/^[rRuU]/.test(text)) {
604
+ text = text.slice(1);
605
+ }
531
606
  let content = text;
532
607
  if (content.startsWith('"""')) {
533
608
  content = content.slice(3, -3);
@@ -716,6 +791,8 @@ function transformNode(node, ctx) {
716
791
  return transformExpressionStatement(node, ctx);
717
792
  case "AssignStatement":
718
793
  return transformAssignStatement(node, ctx);
794
+ case "UpdateStatement":
795
+ return transformUpdateStatement(node, ctx);
719
796
  case "BinaryExpression":
720
797
  return transformBinaryExpression(node, ctx);
721
798
  case "UnaryExpression":
@@ -732,12 +809,14 @@ function transformNode(node, ctx) {
732
809
  return transformString(node, ctx);
733
810
  case "FormatString":
734
811
  return transformFormatString(node, ctx);
812
+ case "ContinuedString":
813
+ return transformContinuedString(node, ctx);
735
814
  case "Boolean":
736
815
  return transformBoolean(node, ctx);
737
816
  case "None":
738
817
  return "null";
739
818
  case "VariableName":
740
- return getNodeText(node, ctx.source);
819
+ return escapeReservedKeyword(getNodeText(node, ctx.source));
741
820
  case "CallExpression":
742
821
  return transformCallExpression(node, ctx);
743
822
  case "MemberExpression":
@@ -802,6 +881,9 @@ function transformNode(node, ctx) {
802
881
  return transformAssertStatement(node, ctx);
803
882
  case "YieldStatement":
804
883
  return transformYieldStatement(node, ctx);
884
+ case "Ellipsis":
885
+ ctx.usesRuntime.add("Ellipsis");
886
+ return "Ellipsis";
805
887
  /* v8 ignore next 2 -- fallback for unknown AST nodes @preserve */
806
888
  default:
807
889
  return getNodeText(node, ctx.source);
@@ -947,6 +1029,23 @@ function extractVariableNames(nodes, source) {
947
1029
  }
948
1030
  return names;
949
1031
  }
1032
+ function transformUpdateStatement(node, ctx) {
1033
+ const children = getChildren(node);
1034
+ const target = children.find(
1035
+ (c) => c.name === "VariableName" || c.name === "MemberExpression" || c.name === "Subscript"
1036
+ );
1037
+ const op = children.find((c) => c.name === "UpdateOp");
1038
+ const value = children.find(
1039
+ (c) => c !== target && c.name !== "UpdateOp" && c.name !== "(" && c.name !== ")" && c.name !== "," && c.name !== ":"
1040
+ );
1041
+ if (!target || !op || !value) {
1042
+ return getNodeText(node, ctx.source);
1043
+ }
1044
+ const targetCode = transformNode(target, ctx);
1045
+ const opText = getNodeText(op, ctx.source);
1046
+ const valueCode = transformNode(value, ctx);
1047
+ return `${targetCode} ${opText} ${valueCode}`;
1048
+ }
950
1049
  function isSliceExpression(node) {
951
1050
  const children = getChildren(node);
952
1051
  return children.some((c) => c.name === ":");
@@ -1239,6 +1338,51 @@ function transformFormatString(node, ctx) {
1239
1338
  result += "`";
1240
1339
  return result;
1241
1340
  }
1341
+ function transformContinuedString(node, ctx) {
1342
+ const children = getChildren(node);
1343
+ const hasFormatString = children.some((c) => c.name === "FormatString");
1344
+ if (hasFormatString) {
1345
+ const parts = children.filter((c) => c.name === "String" || c.name === "FormatString").map((c) => {
1346
+ if (c.name === "FormatString") {
1347
+ return transformFormatString(c, ctx);
1348
+ } else {
1349
+ const text = getNodeText(c, ctx.source);
1350
+ let content;
1351
+ if (/^[rR]['"]/.test(text)) {
1352
+ content = text.slice(2, -1);
1353
+ } else if (/^[rR]"""/.test(text) || /^[rR]'''/.test(text)) {
1354
+ content = text.slice(4, -3);
1355
+ } else if (text.startsWith('"""') || text.startsWith("'''")) {
1356
+ content = text.slice(3, -3);
1357
+ } else {
1358
+ content = text.slice(1, -1);
1359
+ }
1360
+ return "`" + content.replace(/`/g, "\\`") + "`";
1361
+ }
1362
+ });
1363
+ return parts.join(" + ");
1364
+ } else {
1365
+ const parts = children.filter((c) => c.name === "String").map((c) => {
1366
+ const text = getNodeText(c, ctx.source);
1367
+ let content;
1368
+ if (/^[rR]['"]/.test(text)) {
1369
+ content = text.slice(2, -1);
1370
+ } else if (/^[rR]"""/.test(text) || /^[rR]'''/.test(text)) {
1371
+ content = text.slice(4, -3);
1372
+ } else if (text.startsWith('"""') || text.startsWith("'''")) {
1373
+ content = text.slice(3, -3);
1374
+ } else {
1375
+ content = text.slice(1, -1);
1376
+ }
1377
+ return content;
1378
+ });
1379
+ const joined = parts.join("");
1380
+ if (joined.includes('"') && !joined.includes("'")) {
1381
+ return "'" + joined + "'";
1382
+ }
1383
+ return '"' + joined + '"';
1384
+ }
1385
+ }
1242
1386
  function transformBoolean(node, ctx) {
1243
1387
  const text = getNodeText(node, ctx.source);
1244
1388
  return text === "True" ? "true" : "false";
@@ -1839,7 +1983,11 @@ function transformMethodCall(callee, args, ctx) {
1839
1983
  case "rjust":
1840
1984
  return `${objCode}.padStart(${args})`;
1841
1985
  // String split/join - join is special: "sep".join(arr) -> arr.join("sep")
1986
+ // If the argument is a generator expression, convert to array with spread
1842
1987
  case "join":
1988
+ if (args.includes("function*")) {
1989
+ return `[...${args}].join(${objCode})`;
1990
+ }
1843
1991
  return `(${args}).join(${objCode})`;
1844
1992
  case "split":
1845
1993
  return args ? `${objCode}.split(${args})` : `${objCode}.split(/\\s+/)`;
@@ -2040,15 +2188,23 @@ function transformMemberExpression(node, ctx) {
2040
2188
  if (text.includes(":")) {
2041
2189
  return transformSliceFromMember(obj, children, ctx);
2042
2190
  }
2043
- const indexElements = children.filter((c) => c.name !== "[" && c.name !== "]" && c !== obj);
2044
- const index = indexElements[0];
2045
- if (!index) return `${objCode}[]`;
2046
- const indexCode = transformNode(index, ctx);
2047
- if (isNegativeIndexLiteral(index, ctx)) {
2048
- ctx.usesRuntime.add("at");
2049
- return `at(${objCode}, ${indexCode})`;
2191
+ const indexElements = children.filter(
2192
+ (c) => c.name !== "[" && c.name !== "]" && c.name !== "," && c !== obj
2193
+ );
2194
+ if (indexElements.length === 0) return `${objCode}[]`;
2195
+ if (indexElements.length === 1) {
2196
+ const index = indexElements[0];
2197
+ if (!index) return `${objCode}[]`;
2198
+ const indexCode = transformNode(index, ctx);
2199
+ if (isNegativeIndexLiteral(index, ctx)) {
2200
+ ctx.usesRuntime.add("at");
2201
+ return `at(${objCode}, ${indexCode})`;
2202
+ }
2203
+ return `${objCode}[${indexCode}]`;
2050
2204
  }
2051
- return `${objCode}[${indexCode}]`;
2205
+ ctx.usesRuntime.add("tuple");
2206
+ const indices = indexElements.map((el) => transformNode(el, ctx));
2207
+ return `${objCode}[tuple(${indices.join(", ")})]`;
2052
2208
  } else {
2053
2209
  const prop = children[children.length - 1];
2054
2210
  if (!prop) return getNodeText(node, ctx.source);
@@ -3050,7 +3206,12 @@ function transformWithStatement(node, ctx) {
3050
3206
  body = child;
3051
3207
  break;
3052
3208
  }
3053
- if (child.name !== "as" && child.name !== ":" && child.name !== "VariableName") {
3209
+ if (child.name === "as" || child.name === ":") {
3210
+ i++;
3211
+ continue;
3212
+ }
3213
+ const isContextManagerExpr = child.name !== "VariableName" || children[i + 1]?.name === "as" || children[i + 1]?.name === "Body" || children[i + 1]?.name === ",";
3214
+ if (isContextManagerExpr) {
3054
3215
  const expr = child;
3055
3216
  let varName = null;
3056
3217
  const nextChild = children[i + 1];
@@ -3130,7 +3291,7 @@ function extractParamNames(node, source) {
3130
3291
  if (child.name === "*" || getNodeText(child, source) === "*") {
3131
3292
  const nextChild = children[i + 1];
3132
3293
  if (nextChild?.name === "VariableName") {
3133
- names.push(getNodeText(nextChild, source));
3294
+ names.push(escapeReservedKeyword(getNodeText(nextChild, source)));
3134
3295
  i += 2;
3135
3296
  continue;
3136
3297
  }
@@ -3140,7 +3301,7 @@ function extractParamNames(node, source) {
3140
3301
  if (child.name === "**" || getNodeText(child, source) === "**") {
3141
3302
  const nextChild = children[i + 1];
3142
3303
  if (nextChild?.name === "VariableName") {
3143
- names.push(getNodeText(nextChild, source));
3304
+ names.push(escapeReservedKeyword(getNodeText(nextChild, source)));
3144
3305
  i += 2;
3145
3306
  continue;
3146
3307
  }
@@ -3148,7 +3309,7 @@ function extractParamNames(node, source) {
3148
3309
  continue;
3149
3310
  }
3150
3311
  if (child.name === "VariableName") {
3151
- names.push(getNodeText(child, source));
3312
+ names.push(escapeReservedKeyword(getNodeText(child, source)));
3152
3313
  i++;
3153
3314
  continue;
3154
3315
  }
@@ -3156,7 +3317,7 @@ function extractParamNames(node, source) {
3156
3317
  const paramChildren = getChildren(child);
3157
3318
  const name = paramChildren.find((c) => c.name === "VariableName");
3158
3319
  if (name) {
3159
- names.push(getNodeText(name, source));
3320
+ names.push(escapeReservedKeyword(getNodeText(name, source)));
3160
3321
  }
3161
3322
  i++;
3162
3323
  continue;
@@ -4278,7 +4439,7 @@ function transformParamList(node, ctx) {
4278
4439
  const parseParam = (startIndex) => {
4279
4440
  const child = children[startIndex];
4280
4441
  if (child?.name !== "VariableName") return null;
4281
- const nameCode = getNodeText(child, ctx.source);
4442
+ const nameCode = escapeReservedKeyword(getNodeText(child, ctx.source));
4282
4443
  let tsType = null;
4283
4444
  let defaultValue = null;
4284
4445
  let offset = 1;
@@ -4320,7 +4481,7 @@ function transformParamList(node, ctx) {
4320
4481
  if (child.name === "*" || getNodeText(child, ctx.source) === "*") {
4321
4482
  const nextChild = children[i + 1];
4322
4483
  if (nextChild?.name === "VariableName") {
4323
- const name = getNodeText(nextChild, ctx.source);
4484
+ const name = escapeReservedKeyword(getNodeText(nextChild, ctx.source));
4324
4485
  const typeChild = children[i + 2];
4325
4486
  if (typeChild?.name === "TypeDef") {
4326
4487
  const tsType = extractTypeAnnotation(typeChild, ctx);
@@ -4340,7 +4501,7 @@ function transformParamList(node, ctx) {
4340
4501
  if (child.name === "**" || getNodeText(child, ctx.source) === "**") {
4341
4502
  const nextChild = children[i + 1];
4342
4503
  if (nextChild?.name === "VariableName") {
4343
- kwargsParam = getNodeText(nextChild, ctx.source);
4504
+ kwargsParam = escapeReservedKeyword(getNodeText(nextChild, ctx.source));
4344
4505
  i += 2;
4345
4506
  continue;
4346
4507
  }
@@ -4365,7 +4526,7 @@ function transformParamList(node, ctx) {
4365
4526
  const typeDef = paramChildren.find((c) => c.name === "TypeDef");
4366
4527
  const defaultVal = paramChildren[paramChildren.length - 1];
4367
4528
  if (name) {
4368
- const nameCode = getNodeText(name, ctx.source);
4529
+ const nameCode = escapeReservedKeyword(getNodeText(name, ctx.source));
4369
4530
  const tsType = extractTypeAnnotation(typeDef, ctx);
4370
4531
  let defaultValue = null;
4371
4532
  if (defaultVal && name !== defaultVal && defaultVal.name !== "TypeDef") {
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  transpileAsync
4
- } from "../chunk-RNRRQS6K.js";
4
+ } from "../chunk-2VKIPKNY.js";
5
5
 
6
6
  // src/cli/index.ts
7
7
  import { parseArgs } from "util";
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  transpile,
13
13
  transpileAsync,
14
14
  walkTree
15
- } from "./chunk-RNRRQS6K.js";
15
+ } from "./chunk-2VKIPKNY.js";
16
16
  export {
17
17
  debugTree,
18
18
  formatCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "python2ts",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "AST-based Python to TypeScript transpiler. Convert Python code to clean, idiomatic TypeScript with full type preservation.",
5
5
  "homepage": "https://sebastian-software.github.io/python2ts/",
6
6
  "repository": {
@@ -53,7 +53,7 @@
53
53
  "eslint": "^9.39.2",
54
54
  "prettier": "^3.8.0",
55
55
  "typescript-eslint": "^8.53.1",
56
- "pythonlib": "2.0.2"
56
+ "pythonlib": "2.0.3"
57
57
  },
58
58
  "devDependencies": {
59
59
  "tsup": "^8.5.1",