rapydscript-ns 0.9.2 → 0.9.4

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.
Files changed (88) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/PYTHON_GAPS.md +352 -0
  3. package/README.md +176 -32
  4. package/TODO.md +1 -128
  5. package/bin/rapydscript +70 -70
  6. package/language-service/index.js +242 -11
  7. package/memory/project_string_impl.md +43 -0
  8. package/package.json +1 -1
  9. package/release/baselib-plain-pretty.js +248 -38
  10. package/release/baselib-plain-ugly.js +8 -8
  11. package/release/compiler.js +778 -277
  12. package/release/signatures.json +30 -30
  13. package/src/ast.pyj +10 -1
  14. package/src/baselib-builtins.pyj +56 -2
  15. package/src/baselib-containers.pyj +25 -1
  16. package/src/baselib-errors.pyj +7 -3
  17. package/src/baselib-internal.pyj +51 -6
  18. package/src/baselib-str.pyj +18 -5
  19. package/src/lib/asyncio.pyj +534 -0
  20. package/src/lib/base64.pyj +399 -0
  21. package/src/lib/bisect.pyj +73 -0
  22. package/src/lib/collections.pyj +228 -4
  23. package/src/lib/csv.pyj +494 -0
  24. package/src/lib/heapq.pyj +98 -0
  25. package/src/lib/html.pyj +382 -0
  26. package/src/lib/http/__init__.pyj +98 -0
  27. package/src/lib/http/client.pyj +304 -0
  28. package/src/lib/http/cookies.pyj +236 -0
  29. package/src/lib/logging.pyj +672 -0
  30. package/src/lib/pprint.pyj +455 -0
  31. package/src/lib/pythonize.pyj +20 -20
  32. package/src/lib/statistics.pyj +0 -0
  33. package/src/lib/string.pyj +357 -0
  34. package/src/lib/textwrap.pyj +329 -0
  35. package/src/lib/urllib/__init__.pyj +14 -0
  36. package/src/lib/urllib/error.pyj +66 -0
  37. package/src/lib/urllib/parse.pyj +475 -0
  38. package/src/lib/urllib/request.pyj +86 -0
  39. package/src/monaco-language-service/analyzer.js +5 -2
  40. package/src/monaco-language-service/completions.js +26 -0
  41. package/src/monaco-language-service/diagnostics.js +203 -4
  42. package/src/monaco-language-service/scope.js +1 -0
  43. package/src/output/codegen.pyj +4 -1
  44. package/src/output/functions.pyj +152 -6
  45. package/src/output/loops.pyj +17 -2
  46. package/src/output/modules.pyj +1 -1
  47. package/src/output/operators.pyj +15 -0
  48. package/src/output/stream.pyj +0 -1
  49. package/src/parse.pyj +108 -24
  50. package/src/tokenizer.pyj +19 -3
  51. package/test/async_generators.pyj +144 -0
  52. package/test/asyncio.pyj +307 -0
  53. package/test/base64.pyj +202 -0
  54. package/test/baselib.pyj +23 -0
  55. package/test/bisect.pyj +178 -0
  56. package/test/chainmap.pyj +185 -0
  57. package/test/csv.pyj +405 -0
  58. package/test/float_special.pyj +64 -0
  59. package/test/heapq.pyj +174 -0
  60. package/test/html.pyj +212 -0
  61. package/test/http.pyj +259 -0
  62. package/test/imports.pyj +79 -72
  63. package/test/logging.pyj +356 -0
  64. package/test/long.pyj +130 -0
  65. package/test/parenthesized_with.pyj +141 -0
  66. package/test/pprint.pyj +232 -0
  67. package/test/python_compat.pyj +3 -5
  68. package/test/python_modulo.pyj +76 -0
  69. package/test/python_modulo_off.pyj +21 -0
  70. package/test/statistics.pyj +224 -0
  71. package/test/str.pyj +14 -0
  72. package/test/string.pyj +245 -0
  73. package/test/textwrap.pyj +172 -0
  74. package/test/type_display.pyj +48 -0
  75. package/test/type_enforcement.pyj +164 -0
  76. package/test/unit/index.js +94 -6
  77. package/test/unit/language-service-completions.js +121 -0
  78. package/test/unit/language-service-scope.js +32 -0
  79. package/test/unit/language-service.js +190 -5
  80. package/test/unit/run-language-service.js +17 -3
  81. package/test/unit/web-repl.js +2401 -13
  82. package/test/urllib.pyj +193 -0
  83. package/tools/compile.js +1 -1
  84. package/tools/embedded_compiler.js +7 -7
  85. package/tools/export.js +4 -2
  86. package/web-repl/main.js +1 -1
  87. package/web-repl/rapydscript.js +7 -5
  88. package/test/omit_function_metadata.pyj +0 -20
@@ -1831,13 +1831,13 @@ assrt.equal(fib(15), 610)
1831
1831
 
1832
1832
  {
1833
1833
  name: "print_compiles_to_console_log",
1834
- description: "print(x) compiles directly to console.log(x)",
1834
+ description: "print(x) compiles to console.log(ρσ_str(x)) for Python-style string conversion",
1835
1835
  src: [
1836
1836
  "# globals: assrt",
1837
1837
  "print('hello')",
1838
1838
  "print(1, 2, 3)",
1839
1839
  ].join("\n"),
1840
- js_checks: ["console.log(\"hello\")", "console.log(1, 2, 3)"],
1840
+ js_checks: ["console.log(ρσ_str(\"hello\"))", "console.log(ρσ_str(1), ρσ_str(2), ρσ_str(3))"],
1841
1841
  },
1842
1842
 
1843
1843
  {
@@ -1883,7 +1883,7 @@ assrt.equal(fib(15), 610)
1883
1883
  "print('test')",
1884
1884
  ].join("\n"),
1885
1885
  // The compiled JS must NOT contain 'var print = ' (which would overwrite window.print)
1886
- js_checks: [/console\.log\("test"\)/],
1886
+ js_checks: [/console\.log\(ρσ_str\("test"\)\)/],
1887
1887
  },
1888
1888
 
1889
1889
  {
@@ -5275,6 +5275,86 @@ assrt.equal(fib(15), 610)
5275
5275
  js_checks: [/let\s[^;]*ρσ_with_exception/, /let\s[^;]*ρσ_with_suppress/],
5276
5276
  },
5277
5277
 
5278
+ // ── BigInt literals ────────────────────────────────────────────────────
5279
+
5280
+ {
5281
+ name: "bigint_literal_basic",
5282
+ description: "42n compiles to JS BigInt literal and has bigint type",
5283
+ src: [
5284
+ "# globals: assrt",
5285
+ "x = 42n",
5286
+ "assrt.ok(jstype(x) == 'bigint', 'type is bigint')",
5287
+ "assrt.ok(x == 42n, 'value is 42n')",
5288
+ ].join("\n"),
5289
+ js_checks: ["42n"],
5290
+ },
5291
+
5292
+ {
5293
+ name: "bigint_literal_hex",
5294
+ description: "0xFFn compiles to JS hex BigInt literal",
5295
+ src: [
5296
+ "# globals: assrt",
5297
+ "x = 0xFFn",
5298
+ "assrt.ok(x == 255n, 'hex bigint value')",
5299
+ ].join("\n"),
5300
+ js_checks: ["0xFFn"],
5301
+ },
5302
+
5303
+ {
5304
+ name: "bigint_literal_binary",
5305
+ description: "0b1010n compiles to JS binary BigInt literal",
5306
+ src: [
5307
+ "# globals: assrt",
5308
+ "x = 0b1010n",
5309
+ "assrt.ok(x == 10n, 'binary bigint value')",
5310
+ ].join("\n"),
5311
+ js_checks: ["0b1010n"],
5312
+ },
5313
+
5314
+ {
5315
+ name: "bigint_literal_octal",
5316
+ description: "0o77n compiles to JS octal BigInt literal",
5317
+ src: [
5318
+ "# globals: assrt",
5319
+ "x = 0o77n",
5320
+ "assrt.ok(x == 63n, 'octal bigint value')",
5321
+ ].join("\n"),
5322
+ js_checks: ["0o77n"],
5323
+ },
5324
+
5325
+ {
5326
+ name: "bigint_literal_zero",
5327
+ description: "0n compiles and works",
5328
+ src: [
5329
+ "# globals: assrt",
5330
+ "x = 0n",
5331
+ "assrt.ok(jstype(x) == 'bigint', 'type is bigint')",
5332
+ "assrt.ok(x == 0n, 'value is 0n')",
5333
+ ].join("\n"),
5334
+ js_checks: ["0n"],
5335
+ },
5336
+
5337
+ {
5338
+ name: "bigint_literal_arithmetic",
5339
+ description: "BigInt arithmetic works correctly",
5340
+ src: [
5341
+ "# globals: assrt",
5342
+ "x = 10n + 3n",
5343
+ "assrt.ok(x == 13n, 'addition')",
5344
+ ].join("\n"),
5345
+ },
5346
+
5347
+ {
5348
+ name: "bigint_literal_large",
5349
+ description: "Large BigInt literal preserves precision",
5350
+ src: [
5351
+ "# globals: assrt",
5352
+ "x = 999999999999999999999n",
5353
+ "assrt.ok(x == 999999999999999999999n, 'large bigint')",
5354
+ ].join("\n"),
5355
+ js_checks: ["999999999999999999999n"],
5356
+ },
5357
+
5278
5358
  {
5279
5359
  name: "with_statement_suppresses_exception",
5280
5360
  description: "with statement __exit__ returning True suppresses the exception",
@@ -5376,12 +5456,20 @@ function run_tests(filter) {
5376
5456
  " – " + test.description);
5377
5457
  });
5378
5458
 
5459
+ var passed = tests.length - failures.length;
5379
5460
  console.log("");
5380
5461
  if (failures.length) {
5381
- console.log(colored(failures.length + " test(s) failed.", "red"));
5382
- } else {
5383
- console.log(colored("All " + tests.length + " unit tests passed!", "green"));
5462
+ console.log(colored("Failed tests:", "red"));
5463
+ failures.forEach(function (name) {
5464
+ console.log(colored(" " + name, "red"));
5465
+ });
5466
+ console.log("");
5384
5467
  }
5468
+ var summary = "unit tests — " +
5469
+ colored("passed: " + passed, "green") + " " +
5470
+ (failures.length ? colored("failed: " + failures.length, "red") : colored("failed: 0", "green")) +
5471
+ " total: " + tests.length;
5472
+ console.log(summary);
5385
5473
  process.exit(failures.length ? 1 : 0);
5386
5474
  }
5387
5475
 
@@ -87,6 +87,10 @@ function make_tests(CompletionEngine, detect_context, SourceAnalyzer, DtsRegistr
87
87
  require('path').join(__dirname, '../../src/lib/collections.pyj'), 'utf-8'
88
88
  );
89
89
 
90
+ var RE_SRC = require('fs').readFileSync(
91
+ require('path').join(__dirname, '../../src/lib/re.pyj'), 'utf-8'
92
+ );
93
+
90
94
  var TESTS = [
91
95
 
92
96
  // ── detect_context ────────────────────────────────────────────────
@@ -965,6 +969,7 @@ function make_tests(CompletionEngine, detect_context, SourceAnalyzer, DtsRegistr
965
969
  assert_has(list, 'Counter', 'Counter in collections completions');
966
970
  assert_has(list, 'OrderedDict', 'OrderedDict in collections completions');
967
971
  assert_has(list, 'defaultdict', 'defaultdict in collections completions');
972
+ assert_has(list, 'ChainMap', 'ChainMap in collections completions');
968
973
  },
969
974
  },
970
975
 
@@ -1254,6 +1259,122 @@ function make_tests(CompletionEngine, detect_context, SourceAnalyzer, DtsRegistr
1254
1259
  },
1255
1260
  },
1256
1261
 
1262
+ // ── Bare import dot completions (import X; X.attr) ──────────────
1263
+
1264
+ {
1265
+ name: "bare_import_virtual_dot_completions",
1266
+ description: "import mymod; mymod. shows module-level symbols from virtual file",
1267
+ run: function () {
1268
+ var vf = {
1269
+ mymod: [
1270
+ "def foo():",
1271
+ " return 1",
1272
+ "def bar():",
1273
+ " return 2",
1274
+ "BAZ = 42",
1275
+ ].join("\n"),
1276
+ };
1277
+ var engine = make_engine(vf);
1278
+ var analyzer = new SourceAnalyzer(RS);
1279
+ var scopeMap = analyzer.analyze("import mymod\npass", { virtualFiles: vf });
1280
+ var list = engine.getCompletions(scopeMap, pos(2, 7), "mymod.", MockKind);
1281
+ assert_has(list, 'foo', 'foo from virtual module');
1282
+ assert_has(list, 'bar', 'bar from virtual module');
1283
+ assert_has(list, 'BAZ', 'BAZ from virtual module');
1284
+ },
1285
+ },
1286
+
1287
+ {
1288
+ name: "bare_import_virtual_dot_prefix",
1289
+ description: "import mymod; mymod.f filters completions by prefix",
1290
+ run: function () {
1291
+ var vf = {
1292
+ mymod: "def foo():\n return 1\ndef bar():\n return 2",
1293
+ };
1294
+ var engine = make_engine(vf);
1295
+ var analyzer = new SourceAnalyzer(RS);
1296
+ var scopeMap = analyzer.analyze("import mymod\npass", { virtualFiles: vf });
1297
+ var list = engine.getCompletions(scopeMap, pos(2, 8), "mymod.f", MockKind);
1298
+ assert_has(list, 'foo', 'foo matches prefix f');
1299
+ assert_missing(list,'bar', 'bar does not match prefix f');
1300
+ },
1301
+ },
1302
+
1303
+ {
1304
+ name: "bare_import_re_dot_completions",
1305
+ description: "import re; re. shows re module exports via stdlibFiles",
1306
+ run: function () {
1307
+ var engine = make_engine({}, ['print', 'len', 'range'], null, null, { re: RE_SRC });
1308
+ var analyzer = new SourceAnalyzer(RS);
1309
+ var scopeMap = analyzer.analyze("import re\npass", { virtualFiles: { re: RE_SRC } });
1310
+ var list = engine.getCompletions(scopeMap, pos(2, 4), "re.", MockKind);
1311
+ assert_has(list, 'match', 'match from re module');
1312
+ assert_has(list, 'search', 'search from re module');
1313
+ assert_has(list, 'compile', 'compile from re module');
1314
+ assert_has(list, 'sub', 'sub from re module');
1315
+ assert_has(list, 'findall', 'findall from re module');
1316
+ assert_has(list, 'split', 'split from re module');
1317
+ },
1318
+ },
1319
+
1320
+ {
1321
+ name: "bare_import_re_dot_prefix",
1322
+ description: "import re; re.ma filters to matching names like match",
1323
+ run: function () {
1324
+ var engine = make_engine({}, ['print', 'len', 'range'], null, null, { re: RE_SRC });
1325
+ var analyzer = new SourceAnalyzer(RS);
1326
+ var scopeMap = analyzer.analyze("import re\npass", { virtualFiles: { re: RE_SRC } });
1327
+ var list = engine.getCompletions(scopeMap, pos(2, 6), "re.ma", MockKind);
1328
+ assert_has(list, 'match', 'match matches prefix ma');
1329
+ assert_missing(list,'compile', 'compile does not match prefix ma');
1330
+ },
1331
+ },
1332
+
1333
+ {
1334
+ name: "bare_import_alias_dot_completions",
1335
+ description: "import mymod as mm; mm. shows module-level symbols",
1336
+ run: function () {
1337
+ var vf = {
1338
+ mymod: "def foo():\n return 1\nVAL = 10",
1339
+ };
1340
+ var engine = make_engine(vf);
1341
+ var analyzer = new SourceAnalyzer(RS);
1342
+ var scopeMap = analyzer.analyze("import mymod as mm\npass", { virtualFiles: vf });
1343
+ var list = engine.getCompletions(scopeMap, pos(2, 4), "mm.", MockKind);
1344
+ assert_has(list, 'foo', 'foo from aliased module');
1345
+ assert_has(list, 'VAL', 'VAL from aliased module');
1346
+ },
1347
+ },
1348
+
1349
+ {
1350
+ name: "bare_import_unknown_module_empty",
1351
+ description: "import unknown; unknown. returns empty when module source unavailable",
1352
+ run: function () {
1353
+ var engine = make_engine({});
1354
+ var analyzer = new SourceAnalyzer(RS);
1355
+ var scopeMap = analyzer.analyze("import unknown\npass", {});
1356
+ var list = engine.getCompletions(scopeMap, pos(2, 9), "unknown.", MockKind);
1357
+ assert.strictEqual(list.suggestions.length, 0, 'unknown module → no dot suggestions');
1358
+ },
1359
+ },
1360
+
1361
+ {
1362
+ name: "bare_import_collections_dot_completions",
1363
+ description: "import collections; collections. shows namedtuple, deque, Counter, etc.",
1364
+ run: function () {
1365
+ var engine = make_engine({}, ['print', 'len', 'range'], null, null, { collections: COLLECTIONS_SRC });
1366
+ var analyzer = new SourceAnalyzer(RS);
1367
+ var scopeMap = analyzer.analyze("import collections\npass", { virtualFiles: { collections: COLLECTIONS_SRC } });
1368
+ var list = engine.getCompletions(scopeMap, pos(2, 13), "collections.", MockKind);
1369
+ assert_has(list, 'namedtuple', 'namedtuple from collections');
1370
+ assert_has(list, 'deque', 'deque from collections');
1371
+ assert_has(list, 'Counter', 'Counter from collections');
1372
+ assert_has(list, 'OrderedDict', 'OrderedDict from collections');
1373
+ assert_has(list, 'defaultdict', 'defaultdict from collections');
1374
+ assert_has(list, 'ChainMap', 'ChainMap from collections');
1375
+ },
1376
+ },
1377
+
1257
1378
  {
1258
1379
  name: "return_type_imported_fn_inferred_consistent_branches",
1259
1380
  description: "imported fn with multiple return [] branches — type still inferred as list",
@@ -613,6 +613,38 @@ function make_tests(SourceAnalyzer, RS) {
613
613
  },
614
614
  },
615
615
 
616
+ {
617
+ name: "async_generator_local_var_visible",
618
+ description: "Inside `async def` with `yield`, local vars are tracked in scope",
619
+ run: function () {
620
+ // Async generators (async def + yield) should not break scope analysis.
621
+ // Local assignments should be picked up the same as in regular functions.
622
+ var m = analyze([
623
+ "async def aiter():",
624
+ " counter = 0",
625
+ " yield counter",
626
+ " counter += 1",
627
+ " yield counter",
628
+ ].join("\n"));
629
+ var sym = find(m.getAllSymbols(), "counter");
630
+ assert.ok(sym, "Expected 'counter' symbol in async-generator scope");
631
+ },
632
+ },
633
+
634
+ {
635
+ name: "async_for_loop_var_visible",
636
+ description: "`async for x in ...` loop variable is registered in scope",
637
+ run: function () {
638
+ var m = analyze([
639
+ "async def consume(it):",
640
+ " async for item in it:",
641
+ " print(item)",
642
+ ].join("\n"));
643
+ var sym = find(m.getAllSymbols(), "item");
644
+ assert.ok(sym, "Expected 'item' loop variable in async-for scope");
645
+ },
646
+ },
647
+
616
648
  {
617
649
  name: "inferred_class_no_inference_for_variable_rhs",
618
650
  description: "x = some_var does not set inferred_class (unknown rhs)",
@@ -61,12 +61,27 @@ var LIB_DIR = path.join(__dirname, "../../src/lib");
61
61
 
62
62
  /**
63
63
  * Return the module names of all .pyj files currently in src/lib/.
64
+ * Also includes sub-package directory names that contain an __init__.pyj.
64
65
  * Excludes cache files (.pyj-cached) and any non-.pyj entries.
65
66
  */
66
67
  function get_lib_module_names() {
67
- return fs.readdirSync(LIB_DIR)
68
- .filter(function (f) { return f.endsWith(".pyj") && !f.endsWith(".pyj-cached"); })
69
- .map(function (f) { return f.replace(/\.pyj$/, ""); });
68
+ var names = [];
69
+ fs.readdirSync(LIB_DIR).forEach(function (f) {
70
+ if (f.endsWith(".pyj-cached")) return;
71
+ if (f.endsWith(".pyj")) {
72
+ names.push(f.replace(/\.pyj$/, ""));
73
+ } else {
74
+ // Directory with __init__.pyj counts as a package module.
75
+ var full = path.join(LIB_DIR, f);
76
+ try {
77
+ var stat = fs.statSync(full);
78
+ if (stat.isDirectory() && fs.existsSync(path.join(full, "__init__.pyj"))) {
79
+ names.push(f);
80
+ }
81
+ } catch (e) {}
82
+ }
83
+ });
84
+ return names;
70
85
  }
71
86
 
72
87
  function make_tests(Diagnostics, RS, STDLIB_MODULES) {
@@ -885,13 +900,14 @@ function make_tests(Diagnostics, RS, STDLIB_MODULES) {
885
900
  description: "Importing all collections classes produces no errors",
886
901
  run: function () {
887
902
  var markers = d().check([
888
- "from collections import defaultdict, Counter, OrderedDict, deque, namedtuple",
903
+ "from collections import defaultdict, Counter, OrderedDict, deque, namedtuple, ChainMap",
889
904
  "d = defaultdict(list)",
890
905
  "c = Counter([1, 2, 2, 3])",
891
906
  "od = OrderedDict()",
892
907
  "dq = deque([1, 2, 3])",
893
908
  "Point = namedtuple('Point', 'x y')",
894
- "print(d, c, od, dq, Point)",
909
+ "cm = ChainMap({'a': 1}, {'b': 2})",
910
+ "print(d, c, od, dq, Point, cm)",
895
911
  ].join("\n"),
896
912
  { virtualFiles: { mymod: "def foo(): pass" } }
897
913
  );
@@ -915,6 +931,36 @@ function make_tests(Diagnostics, RS, STDLIB_MODULES) {
915
931
  },
916
932
  },
917
933
 
934
+ {
935
+ name: "stdlib_statistics_no_bad_import",
936
+ description: "Importing and using statistics functions produces no errors",
937
+ run: function () {
938
+ var markers = d().check([
939
+ "from statistics import mean, median, mode, stdev, variance",
940
+ "from statistics import quantiles, correlation, linear_regression",
941
+ "from statistics import NormalDist, StatisticsError",
942
+ "data = [1, 2, 3, 4, 5]",
943
+ "m = mean(data)",
944
+ "md = median(data)",
945
+ "mo = mode(data)",
946
+ "sd = stdev(data)",
947
+ "v = variance(data)",
948
+ "q = quantiles(data)",
949
+ "c = correlation(data, data)",
950
+ "lr = linear_regression(data, data)",
951
+ "nd = NormalDist(0, 1)",
952
+ "print(m, md, mo, sd, v, q, c, lr.slope, lr.intercept)",
953
+ "print(nd.mean, nd.stdev, nd.cdf(0), nd.pdf(0), StatisticsError)",
954
+ ].join("\n"),
955
+ { virtualFiles: { sentinel: "x = 1" } }
956
+ );
957
+ var errors = markers.filter(function (m) { return m.severity === SEV_ERROR; });
958
+ assert.deepStrictEqual(errors, [],
959
+ "Expected no errors for statistics imports and usage, got: " +
960
+ JSON.stringify(errors.map(function (m) { return m.message; })));
961
+ },
962
+ },
963
+
918
964
  {
919
965
  name: "copy_no_bad_import",
920
966
  description: "from copy import copy, deepcopy produces no bad-import error",
@@ -1467,6 +1513,145 @@ function make_tests(Diagnostics, RS, STDLIB_MODULES) {
1467
1513
  },
1468
1514
  },
1469
1515
 
1516
+ // ── Infinite-loop detection ───────────────────────────────────────
1517
+
1518
+ {
1519
+ name: "infinite_loop_while_true",
1520
+ description: "while True: with no await inside is flagged as a warning",
1521
+ run: function () {
1522
+ var markers = d().check([
1523
+ "while True:",
1524
+ " x = 1",
1525
+ ].join("\n"));
1526
+ var warnings = markers.filter(function (m) {
1527
+ return m.severity === SEV_WARNING &&
1528
+ m.message.indexOf("while loop") !== -1;
1529
+ });
1530
+ assert.ok(warnings.length >= 1,
1531
+ "Expected an infinite-loop warning but got: " + JSON.stringify(markers));
1532
+ },
1533
+ },
1534
+
1535
+ {
1536
+ name: "infinite_loop_while_1",
1537
+ description: "while 1: with no await inside is flagged as a warning",
1538
+ run: function () {
1539
+ var markers = d().check([
1540
+ "while 1:",
1541
+ " pass",
1542
+ ].join("\n"));
1543
+ var warnings = markers.filter(function (m) {
1544
+ return m.severity === SEV_WARNING &&
1545
+ m.message.indexOf("while loop") !== -1;
1546
+ });
1547
+ assert.ok(warnings.length >= 1,
1548
+ "Expected an infinite-loop warning for while 1: but got: " + JSON.stringify(markers));
1549
+ },
1550
+ },
1551
+
1552
+ {
1553
+ name: "infinite_loop_suppressed_by_await",
1554
+ description: "while True: containing an await does NOT produce a warning",
1555
+ run: function () {
1556
+ var markers = d().check([
1557
+ "async def run():",
1558
+ " while True:",
1559
+ " await some_task()",
1560
+ ].join("\n"));
1561
+ var inf = markers.filter(function (m) {
1562
+ return m.message.indexOf("while loop") !== -1;
1563
+ });
1564
+ assert.deepStrictEqual(inf, [],
1565
+ "Expected no infinite-loop warning when await is present but got: " + JSON.stringify(inf));
1566
+ },
1567
+ },
1568
+
1569
+ {
1570
+ name: "async_generator_no_diagnostics",
1571
+ description: "async def with yield (async generator) parses cleanly with no syntax errors",
1572
+ run: function () {
1573
+ var markers = d().check([
1574
+ "async def aiter():",
1575
+ " yield 1",
1576
+ " yield 2",
1577
+ "",
1578
+ "async def consume():",
1579
+ " out = []",
1580
+ " async for x in aiter():",
1581
+ " out.append(x)",
1582
+ " return out",
1583
+ ].join("\n"));
1584
+ var errs = markers.filter(function (m) { return m.severity === 1 || m.severity === 8; });
1585
+ assert.deepStrictEqual(errs, [],
1586
+ "Expected no syntax errors for async generator + async for, got: " + JSON.stringify(errs));
1587
+ },
1588
+ },
1589
+
1590
+ {
1591
+ name: "async_generator_local_no_undef",
1592
+ description: "Local vars inside an async generator (with `await` and `yield`) resolve correctly",
1593
+ run: function () {
1594
+ var markers = d().check([
1595
+ "async def stream():",
1596
+ " n = await Promise.resolve(3)",
1597
+ " i = 0",
1598
+ " while i < n:",
1599
+ " yield i",
1600
+ " i += 1",
1601
+ ].join("\n"));
1602
+ var undef = markers.filter(function (m) { return m.message.indexOf("Undefined symbol") !== -1; });
1603
+ assert.deepStrictEqual(undef, [],
1604
+ "Expected no 'Undefined symbol' errors in async generator, got: " + JSON.stringify(undef));
1605
+ },
1606
+ },
1607
+
1608
+ {
1609
+ name: "infinite_loop_no_false_positive_for_condition",
1610
+ description: "while x > 0: (non-constant condition) does not produce a warning",
1611
+ run: function () {
1612
+ var markers = d().check([
1613
+ "x = 10",
1614
+ "while x > 0:",
1615
+ " x -= 1",
1616
+ ].join("\n"));
1617
+ var inf = markers.filter(function (m) {
1618
+ return m.message.indexOf("while loop") !== -1;
1619
+ });
1620
+ assert.deepStrictEqual(inf, [],
1621
+ "Expected no infinite-loop warning for non-constant condition but got: " + JSON.stringify(inf));
1622
+ },
1623
+ },
1624
+
1625
+ // ── BigInt literals ──────────────────────────────────────────────
1626
+
1627
+ {
1628
+ name: "bigint_literal_no_errors",
1629
+ description: "BigInt literals (42n, 0xFFn, 0b1010n, 0o77n) produce no markers",
1630
+ run: function () {
1631
+ var markers = d().check([
1632
+ "x = 42n",
1633
+ "y = 0xFFn",
1634
+ "z = 0b1010n",
1635
+ "w = 0o77n",
1636
+ ].join("\n"));
1637
+ assert.deepStrictEqual(markers, [],
1638
+ "Expected no markers for bigint literals, got: " + JSON.stringify(markers));
1639
+ },
1640
+ },
1641
+
1642
+ {
1643
+ name: "bigint_literal_assignment_no_errors",
1644
+ description: "BigInt literal assignment and arithmetic produce no markers",
1645
+ run: function () {
1646
+ var markers = d().check([
1647
+ "x = 42n",
1648
+ "y = x + 1n",
1649
+ ].join("\n"));
1650
+ assert.deepStrictEqual(markers, [],
1651
+ "Expected no markers for bigint assignment, got: " + JSON.stringify(markers));
1652
+ },
1653
+ },
1654
+
1470
1655
  ];
1471
1656
 
1472
1657
  return TESTS;
@@ -29,7 +29,7 @@ var FILES = [
29
29
  "web-repl.js",
30
30
  ];
31
31
 
32
- var failed = false;
32
+ var failed_files = [];
33
33
 
34
34
  FILES.forEach(function (file) {
35
35
  var result = spawn(
@@ -37,7 +37,21 @@ FILES.forEach(function (file) {
37
37
  [path.join(__dirname, file)],
38
38
  { stdio: "inherit" }
39
39
  );
40
- if (result.status !== 0) failed = true;
40
+ if (result.status !== 0) failed_files.push(file);
41
41
  });
42
42
 
43
- process.exit(failed ? 1 : 0);
43
+ console.log("");
44
+ if (failed_files.length) {
45
+ console.log(colored("Failed test files:", "red"));
46
+ failed_files.forEach(function (file) {
47
+ console.log(colored(" ✗ " + file, "red"));
48
+ });
49
+ console.log("");
50
+ }
51
+ var passed_count = FILES.length - failed_files.length;
52
+ var summary = "test suites — " +
53
+ colored("passed: " + passed_count, "green") + " " +
54
+ (failed_files.length ? colored("failed: " + failed_files.length, "red") : colored("failed: 0", "green")) +
55
+ " total: " + FILES.length;
56
+ console.log(summary);
57
+ process.exit(failed_files.length ? 1 : 0);