rapydscript-ng 0.7.24 → 0.8.1

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 (132) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +80 -26
  3. package/bin/build.ts +117 -0
  4. package/bin/package.json +1 -0
  5. package/bin/rapydscript +65 -62
  6. package/build_wheels.py +133 -0
  7. package/editor-plugins/README.md +80 -0
  8. package/editor-plugins/nvim.lua +321 -0
  9. package/local-agent.md +28 -0
  10. package/package.json +4 -5
  11. package/publish.py +27 -17
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +9027 -8139
  15. package/release/signatures.json +27 -27
  16. package/release/stdlib_modules.json +1 -0
  17. package/src/ast.pyj +428 -280
  18. package/src/baselib-builtins.pyj +49 -28
  19. package/src/baselib-containers.pyj +241 -226
  20. package/src/baselib-errors.pyj +8 -1
  21. package/src/baselib-internal.pyj +38 -23
  22. package/src/baselib-itertools.pyj +13 -7
  23. package/src/baselib-str.pyj +59 -84
  24. package/src/compiler.pyj +4 -4
  25. package/src/errors.pyj +3 -4
  26. package/src/lib/aes.pyj +46 -32
  27. package/src/lib/elementmaker.pyj +11 -9
  28. package/src/lib/encodings.pyj +15 -5
  29. package/src/lib/gettext.pyj +9 -4
  30. package/src/lib/math.pyj +106 -45
  31. package/src/lib/operator.pyj +10 -10
  32. package/src/lib/pythonize.pyj +2 -1
  33. package/src/lib/random.pyj +28 -21
  34. package/src/lib/re.pyj +492 -19
  35. package/src/lib/traceback.pyj +171 -29
  36. package/src/lib/uuid.pyj +2 -2
  37. package/src/output/classes.pyj +28 -27
  38. package/src/output/codegen.pyj +87 -83
  39. package/src/output/comments.pyj +8 -8
  40. package/src/output/exceptions.pyj +20 -21
  41. package/src/output/functions.pyj +66 -64
  42. package/src/output/literals.pyj +24 -23
  43. package/src/output/loops.pyj +84 -142
  44. package/src/output/modules.pyj +55 -68
  45. package/src/output/operators.pyj +40 -29
  46. package/src/output/statements.pyj +21 -15
  47. package/src/output/stream.pyj +82 -56
  48. package/src/output/utils.pyj +12 -8
  49. package/src/parse.pyj +971 -620
  50. package/src/string_interpolation.pyj +5 -3
  51. package/src/tokenizer.pyj +188 -148
  52. package/src/utils.pyj +32 -15
  53. package/test/_treeshake_mod.pyj +30 -0
  54. package/test/_treeshake_side_effect.pyj +9 -0
  55. package/test/ast_serialization.pyj +392 -0
  56. package/test/async.pyj +95 -0
  57. package/test/baselib.pyj +1 -2
  58. package/test/embedded_compiler.pyj +57 -0
  59. package/test/fmt.pyj +291 -0
  60. package/test/generic.pyj +16 -3
  61. package/test/imports.pyj +8 -6
  62. package/test/internationalization.pyj +38 -37
  63. package/test/lint.pyj +120 -117
  64. package/test/lsp.pyj +257 -0
  65. package/test/repl.pyj +70 -65
  66. package/test/sourcemaps.pyj +315 -0
  67. package/test/starargs.pyj +46 -39
  68. package/test/str.pyj +8 -0
  69. package/test/traceback.pyj +263 -0
  70. package/test/treeshaking.pyj +181 -0
  71. package/test/web_repl_export.pyj +88 -0
  72. package/tools/ast_serialize.mjs +557 -0
  73. package/tools/cli.mjs +531 -0
  74. package/tools/compile.mjs +205 -0
  75. package/tools/compiler.mjs +159 -0
  76. package/tools/{completer.js → completer.mjs} +6 -6
  77. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  78. package/tools/export.js +109 -56
  79. package/tools/fmt.mjs +975 -0
  80. package/tools/{gettext.js → gettext.mjs} +46 -53
  81. package/tools/ini.mjs +175 -0
  82. package/tools/{lint.js → lint.mjs} +78 -55
  83. package/tools/lsp.mjs +964 -0
  84. package/tools/lsp_protocol.mjs +259 -0
  85. package/tools/lsp_symbols.mjs +418 -0
  86. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  87. package/tools/{repl.js → repl.mjs} +56 -42
  88. package/tools/{self.js → self.mjs} +71 -46
  89. package/tools/sourcemap.mjs +123 -0
  90. package/tools/test.mjs +252 -0
  91. package/tools/treeshake.mjs +400 -0
  92. package/tools/{utils.js → utils.mjs} +26 -23
  93. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  94. package/tools/web_repl_export.mjs +97 -0
  95. package/tree-sitter/README.md +101 -0
  96. package/tree-sitter/grammar.js +992 -0
  97. package/tree-sitter/package.json +43 -0
  98. package/tree-sitter/queries/highlights.scm +232 -0
  99. package/tree-sitter/queries/indents.scm +64 -0
  100. package/tree-sitter/queries/injections.scm +14 -0
  101. package/tree-sitter/queries/locals.scm +108 -0
  102. package/tree-sitter/src/grammar.json +4976 -0
  103. package/tree-sitter/src/node-types.json +2901 -0
  104. package/tree-sitter/src/parser.c +196465 -0
  105. package/tree-sitter/src/scanner.c +294 -0
  106. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  107. package/tree-sitter/src/tree_sitter/array.h +330 -0
  108. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  109. package/tree-sitter/test/corpus/chaining.txt +99 -0
  110. package/tree-sitter/test/corpus/classes.txt +147 -0
  111. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  112. package/tree-sitter/test/corpus/containers.txt +242 -0
  113. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  114. package/tree-sitter/test/corpus/decorators.txt +64 -0
  115. package/tree-sitter/test/corpus/existential.txt +102 -0
  116. package/tree-sitter/test/corpus/functions.txt +293 -0
  117. package/tree-sitter/test/corpus/imports.txt +117 -0
  118. package/tree-sitter/test/corpus/literals.txt +135 -0
  119. package/tree-sitter/test/corpus/operators.txt +296 -0
  120. package/tree-sitter/test/corpus/statements.txt +189 -0
  121. package/tree-sitter/test/corpus/strings.txt +90 -0
  122. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  123. package/tree-sitter/tree-sitter.json +36 -0
  124. package/web-repl/env.js +35 -23
  125. package/web-repl/main.js +8 -8
  126. package/bin/export +0 -75
  127. package/bin/web-repl-export +0 -102
  128. package/tools/cli.js +0 -523
  129. package/tools/compile.js +0 -184
  130. package/tools/compiler.js +0 -84
  131. package/tools/ini.js +0 -65
  132. package/tools/test.js +0 -110
@@ -0,0 +1,57 @@
1
+ # globals: assrt, rs_create_embedded_compiler, fs
2
+
3
+ async def run_tests():
4
+ # Test that create_embedded_compiler() works with no arguments
5
+ compiler = await rs_create_embedded_compiler()
6
+
7
+ # compile must be callable and return non-empty JS
8
+ js = await compiler.compile('x = 1 + 2\n')
9
+ assrt.ok(js and js.length > 0, 'compile must return non-empty JS')
10
+
11
+ # Test compiling a class definition
12
+ js2 = await compiler.compile('class Foo:\n def __init__(self, v):\n self.v = v\n')
13
+ assrt.ok(js2 and js2.length > 0, 'compile must handle class definitions')
14
+
15
+ # Test that successive compile calls accumulate class knowledge (streaming mode)
16
+ compiler2 = await rs_create_embedded_compiler()
17
+ await compiler2.compile('class Bar:\n def __init__(self, v):\n self.v = v\n')
18
+ js3 = await compiler2.compile('b = Bar(42)\n')
19
+ assrt.ok(js3.indexOf('Bar') >= 0, 'subsequent compile should know about previously defined class Bar')
20
+
21
+ # Test virtual_file_system: compile code that imports from a virtual module
22
+ vfs_modules = {
23
+ '__vfs__/testmod.pyj': 'def add_one(x):\n return x + 1\n'
24
+ }
25
+ def vfs_read(path, enc):
26
+ if vfs_modules[path]:
27
+ return Promise.resolve(vfs_modules[path])
28
+ return fs.promises.readFile(path, enc)
29
+
30
+ vfs_compiler = await rs_create_embedded_compiler({
31
+ 'virtual_file_system': {'read_file': vfs_read}
32
+ })
33
+ js4 = await vfs_compiler.compile('from testmod import add_one\nresult = add_one(5)\n')
34
+ assrt.ok(js4 and js4.length > 0, 'compile with vfs import must return JS')
35
+ assrt.ok(js4.indexOf('add_one') >= 0, 'compiled JS must reference the imported function')
36
+
37
+ # Test that stdlib imports are served from the bundled stdlib, not from VFS.
38
+ # The strict VFS below throws ENOENT for any path it does not own, so if the
39
+ # compiler were to route a stdlib read through VFS the import would fail.
40
+ strict_vfs_modules = {
41
+ '__vfs__/testmod.pyj': 'def add_one(x):\n return x + 1\n'
42
+ }
43
+ def strict_vfs_read(path, enc):
44
+ if strict_vfs_modules[path]:
45
+ return Promise.resolve(strict_vfs_modules[path])
46
+ err = new Error('ENOENT: ' + path)
47
+ err.code = 'ENOENT'
48
+ return Promise.reject(err)
49
+
50
+ strict_vfs_compiler = await rs_create_embedded_compiler({
51
+ 'virtual_file_system': {'read_file': strict_vfs_read}
52
+ })
53
+ js5 = await strict_vfs_compiler.compile('from math import sqrt\nresult = sqrt(4)\n')
54
+ assrt.ok(js5 and js5.length > 0, 'stdlib import must succeed when VFS is provided (served from bundled cache)')
55
+ assrt.ok(js5.indexOf('sqrt') >= 0, 'compiled JS must reference the stdlib sqrt function')
56
+
57
+ __test_async_done__ = run_tests()
package/test/fmt.pyj ADDED
@@ -0,0 +1,291 @@
1
+ # globals: assrt, fs, require, test_path, compiler_dir, console, RapydScript
2
+
3
+ # Tests for the "fmt" sub-command (tools/fmt.mjs), the PEP8 style formatter for
4
+ # RapydScript source code.
5
+
6
+ fmt = require('./fmt.mjs')
7
+ path = require('path')
8
+ os = require('os')
9
+
10
+ base_path = path.resolve(compiler_dir, '..')
11
+ libdir = path.join(base_path, 'src', 'lib')
12
+
13
+ async def parses(code):
14
+ ok = True
15
+ try:
16
+ await RapydScript.parse(code, {
17
+ 'filename': 'fmt_test.pyj', 'basedir': test_path,
18
+ 'libdir': libdir, 'for_linting': True
19
+ })
20
+ except:
21
+ ok = False
22
+ console.log('Formatted output failed to parse:\n' + code)
23
+ return ok
24
+
25
+ nchecks = {'n': 0}
26
+
27
+ async def check(name, src, expected, opts):
28
+ opts = opts or {}
29
+ out = fmt.format_string(src, opts)
30
+ assrt.equal(out, expected, 'format output mismatch for: ' + name)
31
+ # The formatter must be idempotent.
32
+ assrt.equal(fmt.format_string(out, opts), out, 'formatter not idempotent for: ' + name)
33
+ # The formatted output must remain valid RapydScript.
34
+ ok = await parses(out)
35
+ assrt.ok(ok, 'formatted output does not parse for: ' + name)
36
+ nchecks.n += 1
37
+
38
+
39
+ async def run_tests():
40
+
41
+ # --- Whitespace / operator normalization -------------------------------
42
+ await check('binary-ops', 'x=1+2*3\n', 'x = 1 + 2 * 3\n')
43
+ await check('collapse-spaces', 'y = f( a ,b )\n', 'y = f(a, b)\n')
44
+ await check('unary-minus', 'x = -1\ny = a - 1\n', 'x = -1\ny = a - 1\n')
45
+ await check('unary-not', 'z = not x\n', 'z = not x\n')
46
+ await check('word-ops', 'q=a and b or c\n', 'q = a and b or c\n')
47
+ await check('is-not', 'r = a is not b\n', 'r = a is not b\n')
48
+ await check('power', 'p = a**b\n', 'p = a ** b\n')
49
+ await check('attribute', 'v = a . b . c\n', 'v = a.b.c\n')
50
+
51
+ # --- kwargs / defaults vs assignment -----------------------------------
52
+ await check('kwargs', 'f( a=1, b = 2 )\n', 'f(a=1, b=2)\n')
53
+ await check('defaults', 'def g(a, b=3, c = 4):\n return a\n',
54
+ 'def g(a, b=3, c=4):\n return a\n')
55
+ await check('assignment-spaces', 'x=1\n', 'x = 1\n')
56
+ await check('augmented', 'x+=1\n', 'x += 1\n')
57
+
58
+ # --- splat / unpacking --------------------------------------------------
59
+ await check('splat', 'f( *args, **kwargs )\n', 'f(*args, **kwargs)\n')
60
+
61
+ # --- slices / index / dict / list --------------------------------------
62
+ await check('slice', 'a = b[ 1 : 2 ]\n', 'a = b[1:2]\n')
63
+ await check('slice-step', 'a = b[::2]\n', 'a = b[::2]\n')
64
+ await check('index', 'c = d[ x ]\n', 'c = d[x]\n')
65
+ await check('negative-index', 'c = d[-1]\n', 'c = d[-1]\n')
66
+ await check('dict', 'e = {"a":1,"b":2}\n', "e = {'a': 1, 'b': 2}\n")
67
+ await check('list', 'l = [ 1,2,3 ]\n', 'l = [1, 2, 3]\n')
68
+
69
+ # --- comprehensions -----------------------------------------------------
70
+ await check('list-comp', 'a=[i*i for i in range(1,20) if i*i%3==0]\n',
71
+ 'a = [i * i for i in range(1, 20) if i * i % 3 == 0]\n')
72
+ await check('dict-comp', 'd={x:x+1 for x in range(20) if x>2}\n',
73
+ 'd = {x: x + 1 for x in range(20) if x > 2}\n')
74
+
75
+ # --- quote normalization -----------------------------------------------
76
+ await check('quote-to-single', 'a = "hi"\n', "a = 'hi'\n")
77
+ await check('quote-to-double', "a = 'hi'\n", 'a = "hi"\n', {'preferred_quote': 'double'})
78
+ # Do not re-quote when it would add escapes.
79
+ await check('quote-keep-apostrophe', 'a = "it\'s"\n', 'a = "it\'s"\n')
80
+ await check('quote-keep-embedded-double', 'a = \'say "hi"\'\n', 'a = \'say "hi"\'\n')
81
+ # Raw / f / verbatim strings are never re-quoted.
82
+ await check('quote-raw-untouched', 'a = r"\\d+"\n', 'a = r"\\d+"\n')
83
+ await check('quote-fstring-untouched', 'a = f"x{y}"\n', 'a = f"x{y}"\n')
84
+
85
+ # --- indentation normalization (tabs and 2-space -> 4-space) -----------
86
+ await check('tab-indent', 'if True:\n\tx = 1\n', 'if True:\n x = 1\n')
87
+ await check('two-space-indent', 'if True:\n x = 1\n if x:\n y = 2\n',
88
+ 'if True:\n x = 1\n if x:\n y = 2\n')
89
+
90
+ # --- blank line policy --------------------------------------------------
91
+ await check('cap-blank-lines', 'a = 1\n\n\n\n\nb = 2\n', 'a = 1\n\n\nb = 2\n')
92
+ await check('two-blanks-between-defs',
93
+ 'def f():\n pass\ndef g():\n pass\n',
94
+ 'def f():\n pass\n\n\ndef g():\n pass\n')
95
+ await check('one-blank-between-methods',
96
+ 'class A:\n def f(self):\n pass\n def g(self):\n pass\n',
97
+ 'class A:\n def f(self):\n pass\n\n def g(self):\n pass\n')
98
+ await check('collapse-def-blanks',
99
+ 'def f():\n pass\n\n\n\n\ndef g():\n pass\n',
100
+ 'def f():\n pass\n\n\ndef g():\n pass\n')
101
+
102
+ # --- comments -----------------------------------------------------------
103
+ await check('comment-space', '#hello\nx = 1\n', '# hello\nx = 1\n')
104
+ await check('trailing-comment', 'x = 1 #note\n', 'x = 1 # note\n')
105
+ await check('comment-before-def',
106
+ '# a helper\ndef f():\n pass\n',
107
+ '# a helper\ndef f():\n pass\n')
108
+
109
+ # --- decorators ---------------------------------------------------------
110
+ await check('decorators',
111
+ '@makebold\n@makeitalic\ndef hello():\n return "hi"\n',
112
+ "@makebold\n@makeitalic\ndef hello():\n return 'hi'\n")
113
+
114
+ # --- line wrapping ------------------------------------------------------
115
+ await check('wrap-call',
116
+ 'result = fn(aaaaaaaa, bbbbbbbb, cccccccc, dddddddd, eeeeeeee)\n',
117
+ 'result = fn(\n aaaaaaaa,\n bbbbbbbb,\n cccccccc,\n dddddddd,\n eeeeeeee\n)\n',
118
+ {'line_length': 30})
119
+ await check('wrap-list-trailing-comma',
120
+ 'data = [11111111, 22222222, 33333333, 44444444]\n',
121
+ 'data = [\n 11111111,\n 22222222,\n 33333333,\n 44444444,\n]\n',
122
+ {'line_length': 20})
123
+ # Short lines are left alone.
124
+ await check('no-wrap-short', 'x = f(a, b)\n', 'x = f(a, b)\n', {'line_length': 80})
125
+ # Comprehensions must never be wrapped on their (tuple-unpacking) commas.
126
+ await check('no-wrap-comprehension',
127
+ 'result = [transform(it) for it, index in enumerate(collection) if it.is_valid]\n',
128
+ 'result = [transform(it) for it, index in enumerate(collection) if it.is_valid]\n',
129
+ {'line_length': 40})
130
+
131
+ # --- reflow of simple multi-line statements ----------------------------
132
+ # By default, source line breaks are preserved (no joining).
133
+ await check('no-join-default',
134
+ 'x = fn(\n a,\n b,\n c\n)\n',
135
+ 'x = fn(\n a,\n b,\n c\n)\n')
136
+ # With join_lines=True the statement is collapsed onto one line.
137
+ await check('reflow-simple-call',
138
+ 'x = fn(\n a,\n b,\n c\n)\n',
139
+ 'x = fn(a, b, c)\n',
140
+ {'join_lines': True})
141
+ # A multi-line statement that is too long even in source form gets wrapped
142
+ # regardless of join_lines.
143
+ await check('no-join-wrap-long',
144
+ 'x = very_long_function_name(\n argument_one,\n argument_two,\n argument_three\n)\n',
145
+ 'x = very_long_function_name(\n argument_one,\n argument_two,\n argument_three\n)\n',
146
+ {'line_length': 40})
147
+
148
+ # --- RapydScript specific constructs are preserved ----------------------
149
+ await check('preserve-anon-function',
150
+ "params = {\n 'onclick': def (event):\n alert('hi')\n ,\n 'x': 5\n}\n",
151
+ "params = {\n 'onclick': def (event):\n alert('hi')\n ,\n 'x': 5\n}\n")
152
+ # Assignments inside an anonymous def passed as an argument must keep spaces
153
+ # around '='; they are not kwargs even though they are inside the outer call.
154
+ await check('anon-def-arg-assignment',
155
+ "window.addEventListener('beforeunload', def (event):\n self.disable=True\n self.ws.close()\n)\n",
156
+ "window.addEventListener('beforeunload', def (event):\n self.disable = True\n self.ws.close()\n)\n")
157
+ # Inline anonymous def inside a call: kwargs that follow the def must not
158
+ # gain spaces (they are not assignments in the def body).
159
+ await check('inline-anon-def-arg-kwargs',
160
+ "xhr = ajax('url', def ():pass;, method='POST', flag=False)\n",
161
+ "xhr = ajax('url', def (): pass;, method='POST', flag=False)\n")
162
+ await check('preserve-chain',
163
+ "$(element)\n.css('background-color', 'red')\n.show()\n",
164
+ "$(element)\n.css('background-color', 'red')\n.show()\n")
165
+ await check('preserve-verbatim-js', "a = v'var x = {foo: 1};'\n", "a = v'var x = {foo: 1};'\n")
166
+ await check('preserve-regex', 'b = /a(b)/g\n', 'b = /a(b)/g\n')
167
+ await check('preserve-verbose-regex',
168
+ "c = re.match(///\n a # comment\n b\n///, 'ab')\n",
169
+ "c = re.match(///\n a # comment\n b\n///, 'ab')\n")
170
+ await check('preserve-fstring', "msg = f'hello {name} world'\n", "msg = f'hello {name} world'\n")
171
+ await check('preserve-triple-string',
172
+ "text = '''\nline one\n line two\n'''\n",
173
+ "text = '''\nline one\n line two\n'''\n")
174
+ await check('preserve-existential',
175
+ 'ans = a?.b?[1]?()\nv = b ? c\n',
176
+ 'ans = a?.b?[1]?()\nv = b ? c\n')
177
+
178
+ # --- backslash continuation is preserved --------------------------------
179
+ await check('preserve-backslash',
180
+ 'x = a + \\\n b + \\\n c\n',
181
+ 'x = a + \\\n b + \\\n c\n')
182
+
183
+ # --- inline anonymous function on a single line -------------------------
184
+ await check('inline-anon', 'add = def(a,b): return a+b\n',
185
+ 'add = def (a, b): return a + b\n')
186
+ # --- anonymous function assignment: space always inserted between def and ( --
187
+ await check('anon-def-assignment',
188
+ "func = def(event):\n pass\n",
189
+ "func = def (event):\n pass\n")
190
+ await check('anon-def-assignment-already-spaced',
191
+ "func = def (event):\n pass\n",
192
+ "func = def (event):\n pass\n")
193
+
194
+ # --- shebang preserved --------------------------------------------------
195
+ await check('shebang', '#!/usr/bin/env rapydscript\nx=1\n',
196
+ '#!/usr/bin/env rapydscript\nx = 1\n')
197
+
198
+ # --- empty / whitespace only input --------------------------------------
199
+ assrt.equal(fmt.format_string('', {}), '', 'empty input -> empty output')
200
+ assrt.equal(fmt.format_string('\n\n\n', {}), '', 'blank input -> empty output')
201
+
202
+ # --- check_report -------------------------------------------------------
203
+ errs = fmt.check_report('a.pyj', 'x=1\n', fmt.format_string('x=1\n', {}), {})
204
+ assrt.ok(errs.length >= 1, 'check_report must flag files that would be reformatted')
205
+ assrt.ok(errs[0].indexOf('would be reformatted') >= 0, 'check_report message content')
206
+
207
+ errs2 = fmt.check_report('a.pyj', 'x = 1\n', 'x = 1\n', {})
208
+ assrt.equal(errs2.length, 0, 'check_report must be silent for already-formatted, short files')
209
+
210
+ longline = 'x = ' + "'" + Array(90).join('a') + "'" + '\n'
211
+ errs3 = fmt.check_report('a.pyj', longline, longline, {'line_length': 80})
212
+ assrt.ok(errs3.length >= 1, 'check_report must flag over-length lines')
213
+ assrt.ok(errs3.join('\n').indexOf('exceeds') >= 0, 'check_report over-length message content')
214
+
215
+ # --- import organization ------------------------------------------------
216
+ # __python__ group is first; stdlib group is second; other group is third.
217
+ # Blank line separates each non-empty group from the next.
218
+ await check('import-organize-three-groups',
219
+ 'import my_module\nimport math\nfrom __python__ import os\n',
220
+ 'from __python__ import os\n\nimport math\n\nimport my_module\n')
221
+ # stdlib (math, re, ...) in group 2; other imports in group 3, blank line between.
222
+ await check('import-organize-two-groups',
223
+ 'import my_module\nimport math\n',
224
+ 'import math\n\nimport my_module\n')
225
+ # from-imports in stdlib group follow bare imports (isort convention).
226
+ await check('import-organize-from-after-bare',
227
+ 'from re import match\nimport math\nimport my_module\n',
228
+ 'import math\nfrom re import match\n\nimport my_module\n')
229
+ # Sorting within a group: alphabetical by module then by names.
230
+ await check('import-organize-sort-stdlib',
231
+ 'import re\nimport math\n',
232
+ 'import math\nimport re\n')
233
+ await check('import-organize-sort-other',
234
+ 'import zebra\nimport apple\n',
235
+ 'import apple\nimport zebra\n')
236
+ # Only stdlib: no blank-line separator.
237
+ await check('import-organize-stdlib-only',
238
+ 'import re\nimport math\n',
239
+ 'import math\nimport re\n')
240
+ # Only other: no blank-line separator.
241
+ await check('import-organize-other-only',
242
+ 'import zebra\nimport apple\n',
243
+ 'import apple\nimport zebra\n')
244
+ # Only __python__: no blank-line separator.
245
+ await check('import-organize-python-only',
246
+ 'from __python__ import sys\nfrom __python__ import os\n',
247
+ 'from __python__ import os\nfrom __python__ import sys\n')
248
+ # __python__ with stdlib only: blank line between them.
249
+ await check('import-organize-python-and-stdlib',
250
+ 'import math\nfrom __python__ import os\n',
251
+ 'from __python__ import os\n\nimport math\n')
252
+ # __python__ with other only: blank line between them.
253
+ await check('import-organize-python-and-other',
254
+ 'import my_module\nfrom __python__ import os\n',
255
+ 'from __python__ import os\n\nimport my_module\n')
256
+ # Already organized: idempotent (checked automatically by check()).
257
+ await check('import-organize-already-organized',
258
+ 'import math\n\nimport my_module\n',
259
+ 'import math\n\nimport my_module\n')
260
+ await check('import-organize-python-already-organized',
261
+ 'from __python__ import os\n\nimport math\n\nimport my_module\n',
262
+ 'from __python__ import os\n\nimport math\n\nimport my_module\n')
263
+ # Code after imports is untouched; blank line between imports and code is preserved.
264
+ await check('import-organize-with-code',
265
+ 'import my_module\nimport math\n\nx = 1\n',
266
+ 'import math\n\nimport my_module\n\nx = 1\n')
267
+ # from-imports within other group are sorted by module then names.
268
+ await check('import-organize-from-other',
269
+ 'from zebra import z\nfrom apple import a\n',
270
+ 'from apple import a\nfrom zebra import z\n')
271
+ # Mixed bare and from-imports across groups.
272
+ await check('import-organize-mixed',
273
+ 'from my_mod import func\nimport re\nimport math\n',
274
+ 'import math\nimport re\n\nfrom my_mod import func\n')
275
+
276
+ # --- collect_pyj_files (directory recursion) ----------------------------
277
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rs-fmt-'))
278
+ fs.mkdirSync(path.join(tmp, 'sub'))
279
+ fs.writeFileSync(path.join(tmp, 'a.pyj'), 'x=1\n')
280
+ fs.writeFileSync(path.join(tmp, 'sub', 'b.pyj'), 'y=2\n')
281
+ fs.writeFileSync(path.join(tmp, 'c.txt'), 'not rapydscript\n')
282
+ collected = await fmt.collect_pyj_files([tmp])
283
+ assrt.equal(collected.length, 2, 'collect_pyj_files must recurse and find 2 .pyj files')
284
+ for f in collected:
285
+ assrt.ok(f.indexOf('.txt') < 0, 'collect_pyj_files must skip non .pyj files in directories')
286
+ # An explicitly named non .pyj file is still included.
287
+ collected2 = await fmt.collect_pyj_files([path.join(tmp, 'c.txt')])
288
+ assrt.equal(collected2.length, 1, 'explicitly named files are always included')
289
+ fs.rmSync(tmp, {'recursive': True})
290
+
291
+ __test_async_done__ = run_tests()
package/test/generic.pyj CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  import traceback
4
4
 
5
+ _throw_test_promises = []
6
+
5
7
  def throw_test(code):
6
- assrt.throws(def():
7
- RapydScript.parse(code, {'filename':code}).body[0]
8
- , RapydScript.SyntaxError)
8
+ _throw_test_promises.append(assrt.rejects(async def():
9
+ await RapydScript.parse(code, {'filename':code})
10
+ , RapydScript.SyntaxError))
9
11
 
10
12
  # unary operators
11
13
  assrt.equal(-(1), -1)
@@ -37,6 +39,15 @@ assrt.equal(-1 < 0 == 1 < 0, False)
37
39
  # Empty tuple
38
40
  assrt.deepEqual((), [])
39
41
 
42
+ # Tuples with newlines inside parentheses
43
+ assrt.deepEqual((1,
44
+ 2), [1, 2])
45
+ assrt.deepEqual((1,
46
+ 2,
47
+ 3), [1, 2, 3])
48
+ assrt.deepEqual((1,
49
+ 2), [1, 2])
50
+
40
51
  # Conditional operators
41
52
  assrt.equal(1 if True else 2, 1)
42
53
  assrt.equal(1
@@ -368,3 +379,5 @@ if "1" + "2" not in ["1", "2"]:
368
379
  assrt.ok(1)
369
380
  else:
370
381
  assrt.ok(0)
382
+
383
+ Promise.all(_throw_test_promises)
package/test/imports.pyj CHANGED
@@ -64,9 +64,11 @@ eq(GLOBAL_SYMBOL, 'i am global')
64
64
  # Import errors happen during parsing, so we cannot test them directly as they would
65
65
  # prevent this file from being parsed.
66
66
 
67
- assrt.throws(def():
68
- RapydScript.parse('from _import_one import not_exported', {'basedir':test_path}).body[0]
69
- , /not exported/)
70
- assrt.throws(def():
71
- RapydScript.parse('import xxxx', {'basedir':test_path}).body[0]
72
- , /doesn't exist/)
67
+ Promise.all([
68
+ assrt.rejects(async def():
69
+ await RapydScript.parse('from _import_one import not_exported', {'basedir':test_path})
70
+ , /not exported/),
71
+ assrt.rejects(async def():
72
+ await RapydScript.parse('import xxxx', {'basedir':test_path})
73
+ , /doesn't exist/),
74
+ ])
@@ -3,30 +3,29 @@
3
3
 
4
4
  from gettext import gettext as _, ngettext, install
5
5
 
6
- g = require('../tools/gettext.js')
7
-
8
- def gettext(code):
6
+ async def gettext(code):
9
7
  ans = {}
10
- g.gettext(ans, code, '<test>')
8
+ await rs_gettext(ans, code, '<test>')
11
9
  return ans
12
10
 
13
- def test_string(code, *args):
14
- catalog = gettext(code)
11
+ async def test_string(code, *args):
12
+ catalog = await gettext(code)
15
13
  assrt.equal(len(catalog), len(args))
16
14
  for msgid, q in zip(Object.keys(catalog), args):
17
- assrt.equal(g.entry_to_string(msgid, catalog[msgid]), q)
15
+ assrt.equal(rs_entry_to_string(msgid, catalog[msgid]), q)
18
16
 
19
- test_string('a = _("one")', '#: <test>:1\nmsgid "one"\nmsgstr ""')
20
- test_string('a = _("one")\nb = gettext("one")', '#: <test>:1\n#: <test>:2\nmsgid "one"\nmsgstr ""')
21
- test_string('''a = _("""one
17
+ async def run_all_tests():
18
+ await test_string('a = _("one")', '#: <test>:1\nmsgid "one"\nmsgstr ""')
19
+ await test_string('a = _("one")\nb = gettext("one")', '#: <test>:1\n#: <test>:2\nmsgid "one"\nmsgstr ""')
20
+ await test_string('''a = _("""one
22
21
  two""")''', '#: <test>:1\nmsgid "one\\ntwo"\nmsgstr ""')
23
- test_string('a = _("{}one")', '#: <test>:1\n#, python-brace-format\nmsgid "{}one"\nmsgstr ""')
24
- test_string('a = _("{one}")', '#: <test>:1\n#, python-brace-format\nmsgid "{one}"\nmsgstr ""')
25
- test_string('ngettext("one", "two", 1)', '#: <test>:1\nmsgid "one"\nmsgid_plural "two"\nmsgstr[0] ""\nmsgstr[1] ""')
26
- test_string('''_('o"ne')''', '#: <test>:1\nmsgid "o\\"ne"\nmsgstr ""')
22
+ await test_string('a = _("{}one")', '#: <test>:1\n#, python-brace-format\nmsgid "{}one"\nmsgstr ""')
23
+ await test_string('a = _("{one}")', '#: <test>:1\n#, python-brace-format\nmsgid "{one}"\nmsgstr ""')
24
+ await test_string('ngettext("one", "two", 1)', '#: <test>:1\nmsgid "one"\nmsgid_plural "two"\nmsgstr[0] ""\nmsgstr[1] ""')
25
+ await test_string('''_('o"ne')''', '#: <test>:1\nmsgid "o\\"ne"\nmsgstr ""')
27
26
 
28
- m = require('../tools/msgfmt.js')
29
- catalog = m.parse(r'''
27
+ m = rs_msgfmt
28
+ catalog = m.parse(r'''
30
29
  msgid ""
31
30
  msgstr ""
32
31
  "Language: en\n"
@@ -48,26 +47,28 @@ msgid "test \"quote\" escape"
48
47
  msgstr "good"
49
48
  ''')
50
49
 
51
- assrt.equal(2, catalog['nplurals'])
52
- assrt.equal('en', catalog['language'])
53
- assrt.equal(catalog['entries'].length, 3)
54
- item = catalog['entries'][0]
55
- assrt.equal(item['msgid'], 'one\ncontinued')
56
- assrt.deepEqual(item['msgstr'], v"['ONE']")
57
- assrt.ok(item['fuzzy'], 'item not fuzzy')
58
- item = catalog['entries'][1]
59
- assrt.equal(item['msgid'], 'two')
60
- assrt.deepEqual(item['msgstr'], v"['abc', 'def']")
61
- assrt.ok(not item['fuzzy'], 'item not fuzzy')
62
- item = catalog['entries'][2]
63
- assrt.equal(item['msgid'], 'test "quote" escape')
64
- assrt.deepEqual(item['msgstr'], v"['good']")
50
+ assrt.equal(2, catalog['nplurals'])
51
+ assrt.equal('en', catalog['language'])
52
+ assrt.equal(catalog['entries'].length, 3)
53
+ item = catalog['entries'][0]
54
+ assrt.equal(item['msgid'], 'one\ncontinued')
55
+ assrt.deepEqual(item['msgstr'], v"['ONE']")
56
+ assrt.ok(item['fuzzy'], 'item not fuzzy')
57
+ item = catalog['entries'][1]
58
+ assrt.equal(item['msgid'], 'two')
59
+ assrt.deepEqual(item['msgstr'], v"['abc', 'def']")
60
+ assrt.ok(not item['fuzzy'], 'item not fuzzy')
61
+ item = catalog['entries'][2]
62
+ assrt.equal(item['msgid'], 'test "quote" escape')
63
+ assrt.deepEqual(item['msgstr'], v"['good']")
64
+
65
+ install({'entries': {
66
+ 'one':['ONE'],
67
+ 'two':['1', '2'],
68
+ }})
65
69
 
66
- install({'entries': {
67
- 'one':['ONE'],
68
- 'two':['1', '2'],
69
- }})
70
+ assrt.equal(_('one'), 'ONE')
71
+ assrt.equal(ngettext('two', 'xxx', 1), '1')
72
+ assrt.equal(ngettext('two', 'xxx', 100), '2')
70
73
 
71
- assrt.equal(_('one'), 'ONE')
72
- assrt.equal(ngettext('two', 'xxx', 1), '1')
73
- assrt.equal(ngettext('two', 'xxx', 100), '2')
74
+ run_all_tests()