rapydscript-ng 0.7.24 → 0.8.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.
Files changed (114) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +72 -26
  3. package/bin/package.json +1 -0
  4. package/bin/rapydscript +65 -62
  5. package/editor-plugins/nvim/rapydscript/README.md +184 -0
  6. package/editor-plugins/nvim/rapydscript/bin/rapydscript.so +0 -0
  7. package/editor-plugins/nvim/rapydscript/ftdetect/rapydscript.lua +10 -0
  8. package/editor-plugins/nvim/rapydscript/lua/rapydscript/health.lua +88 -0
  9. package/editor-plugins/nvim/rapydscript/lua/rapydscript/init.lua +279 -0
  10. package/local-agent.md +28 -0
  11. package/package.json +4 -5
  12. package/release/baselib-plain-pretty.js +448 -326
  13. package/release/baselib-plain-ugly.js +3 -3
  14. package/release/compiler.js +2528 -1672
  15. package/release/signatures.json +17 -17
  16. package/src/ast.pyj +73 -25
  17. package/src/baselib-builtins.pyj +2 -2
  18. package/src/baselib-containers.pyj +153 -151
  19. package/src/baselib-internal.pyj +3 -3
  20. package/src/baselib-str.pyj +5 -5
  21. package/src/lib/aes.pyj +1 -1
  22. package/src/lib/encodings.pyj +1 -1
  23. package/src/lib/pythonize.pyj +1 -1
  24. package/src/lib/re.pyj +2 -2
  25. package/src/lib/traceback.pyj +165 -28
  26. package/src/lib/uuid.pyj +1 -1
  27. package/src/output/codegen.pyj +10 -3
  28. package/src/output/functions.pyj +31 -40
  29. package/src/output/literals.pyj +11 -14
  30. package/src/output/loops.pyj +25 -87
  31. package/src/output/modules.pyj +5 -47
  32. package/src/output/statements.pyj +1 -1
  33. package/src/output/stream.pyj +58 -31
  34. package/src/parse.pyj +777 -427
  35. package/src/tokenizer.pyj +16 -4
  36. package/src/utils.pyj +1 -1
  37. package/test/_treeshake_mod.pyj +30 -0
  38. package/test/_treeshake_side_effect.pyj +9 -0
  39. package/test/ast_serialization.pyj +392 -0
  40. package/test/async.pyj +95 -0
  41. package/test/baselib.pyj +1 -2
  42. package/test/embedded_compiler.pyj +57 -0
  43. package/test/fmt.pyj +201 -0
  44. package/test/generic.pyj +7 -3
  45. package/test/imports.pyj +8 -6
  46. package/test/internationalization.pyj +38 -37
  47. package/test/lint.pyj +120 -117
  48. package/test/lsp.pyj +222 -0
  49. package/test/repl.pyj +70 -65
  50. package/test/sourcemaps.pyj +315 -0
  51. package/test/starargs.pyj +46 -39
  52. package/test/traceback.pyj +263 -0
  53. package/test/treeshaking.pyj +181 -0
  54. package/test/web_repl_export.pyj +88 -0
  55. package/tools/ast_serialize.mjs +557 -0
  56. package/tools/cli.mjs +510 -0
  57. package/tools/compile.mjs +201 -0
  58. package/tools/compiler.mjs +127 -0
  59. package/tools/{completer.js → completer.mjs} +6 -6
  60. package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
  61. package/tools/export.js +109 -56
  62. package/tools/fmt.mjs +735 -0
  63. package/tools/{gettext.js → gettext.mjs} +46 -53
  64. package/tools/{ini.js → ini.mjs} +9 -10
  65. package/tools/{lint.js → lint.mjs} +78 -55
  66. package/tools/lsp.mjs +914 -0
  67. package/tools/lsp_protocol.mjs +259 -0
  68. package/tools/lsp_symbols.mjs +418 -0
  69. package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
  70. package/tools/{repl.js → repl.mjs} +52 -41
  71. package/tools/{self.js → self.mjs} +56 -46
  72. package/tools/sourcemap.mjs +123 -0
  73. package/tools/test.mjs +152 -0
  74. package/tools/treeshake.mjs +400 -0
  75. package/tools/{utils.js → utils.mjs} +26 -23
  76. package/tools/{web_repl.js → web_repl.mjs} +15 -13
  77. package/tools/web_repl_export.mjs +93 -0
  78. package/tree-sitter/README.md +101 -0
  79. package/tree-sitter/grammar.js +992 -0
  80. package/tree-sitter/package.json +43 -0
  81. package/tree-sitter/queries/rapydscript/highlights.scm +232 -0
  82. package/tree-sitter/queries/rapydscript/indents.scm +64 -0
  83. package/tree-sitter/queries/rapydscript/injections.scm +14 -0
  84. package/tree-sitter/queries/rapydscript/locals.scm +108 -0
  85. package/tree-sitter/src/grammar.json +4976 -0
  86. package/tree-sitter/src/node-types.json +2901 -0
  87. package/tree-sitter/src/parser.c +196465 -0
  88. package/tree-sitter/src/scanner.c +294 -0
  89. package/tree-sitter/src/tree_sitter/alloc.h +54 -0
  90. package/tree-sitter/src/tree_sitter/array.h +330 -0
  91. package/tree-sitter/src/tree_sitter/parser.h +286 -0
  92. package/tree-sitter/test/corpus/chaining.txt +99 -0
  93. package/tree-sitter/test/corpus/classes.txt +147 -0
  94. package/tree-sitter/test/corpus/comprehensions.txt +155 -0
  95. package/tree-sitter/test/corpus/containers.txt +242 -0
  96. package/tree-sitter/test/corpus/control_flow.txt +361 -0
  97. package/tree-sitter/test/corpus/decorators.txt +64 -0
  98. package/tree-sitter/test/corpus/existential.txt +102 -0
  99. package/tree-sitter/test/corpus/functions.txt +293 -0
  100. package/tree-sitter/test/corpus/imports.txt +117 -0
  101. package/tree-sitter/test/corpus/literals.txt +135 -0
  102. package/tree-sitter/test/corpus/operators.txt +296 -0
  103. package/tree-sitter/test/corpus/statements.txt +189 -0
  104. package/tree-sitter/test/corpus/strings.txt +90 -0
  105. package/tree-sitter/test/corpus/subscripts.txt +227 -0
  106. package/tree-sitter/tree-sitter.json +36 -0
  107. package/web-repl/env.js +35 -23
  108. package/web-repl/main.js +8 -8
  109. package/bin/export +0 -75
  110. package/bin/web-repl-export +0 -102
  111. package/tools/cli.js +0 -523
  112. package/tools/compile.js +0 -184
  113. package/tools/compiler.js +0 -84
  114. package/tools/test.js +0 -110
package/test/lsp.pyj ADDED
@@ -0,0 +1,222 @@
1
+ # globals: assrt, fs, require, test_path, compiler_dir, console, RapydScript
2
+
3
+ # Tests for the "lsp" sub-command (tools/lsp.mjs), the RapydScript language
4
+ # server. These drive the pure analysis functions directly (the same functions
5
+ # the JSON-RPC layer calls) plus the parser's error-recovery mode.
6
+
7
+ lsp = require('./lsp.mjs')
8
+ path = require('path')
9
+ os = require('os')
10
+ url = require('url')
11
+
12
+ base_path = path.resolve(compiler_dir, '..')
13
+ libdir = path.join(base_path, 'src', 'lib')
14
+
15
+ def uri_of(p):
16
+ return url.pathToFileURL(p).href
17
+
18
+ def offset_of(text, needle, from_index):
19
+ return text.indexOf(needle, from_index or 0)
20
+
21
+ def make_ctx(tmp):
22
+ return lsp.create_server_context({
23
+ 'import_dirs': [tmp], 'libdir': libdir, 'workspace_roots': [tmp],
24
+ 'line_length': 80, 'preferred_quote': 'single',
25
+ })
26
+
27
+ def write_workspace():
28
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rs-lsp-'))
29
+ fs.writeFileSync(path.join(tmp, 'util.pyj'),
30
+ 'def helper(x):\n return x + 1\n\nclass Widget:\n def __init__(self):\n self.n = 0\n')
31
+ fs.writeFileSync(path.join(tmp, 'main.pyj'),
32
+ 'from util import helper\nimport util\n\ndef run():\n w = util.Widget()\n return helper(w)\n\nunused_var = 5\nmissing = undefined_thing()\nimport nonexistent_module\n')
33
+ return tmp
34
+
35
+ def codes(diags):
36
+ ans = {}
37
+ for d in diags:
38
+ ans[d.code] = (ans[d.code] or 0) + 1
39
+ return ans
40
+
41
+ async def test_diagnostics():
42
+ tmp = write_workspace()
43
+ ctx = make_ctx(tmp)
44
+ main_uri = uri_of(path.join(tmp, 'main.pyj'))
45
+ text = fs.readFileSync(path.join(tmp, 'main.pyj'), 'utf-8')
46
+ diags = await lsp.compute_diagnostics(ctx, main_uri, text)
47
+ c = codes(diags)
48
+ assrt.ok(c['undef'], 'expected an undef diagnostic for undefined_thing')
49
+ assrt.ok(c['unused-import'], 'expected unused-import for nonexistent_module')
50
+ assrt.ok(c['import-unresolved'], 'expected import-unresolved for nonexistent_module')
51
+ fs.rmSync(tmp, {'recursive': True})
52
+
53
+ async def test_hover_and_definition():
54
+ tmp = write_workspace()
55
+ ctx = make_ctx(tmp)
56
+ main_uri = uri_of(path.join(tmp, 'main.pyj'))
57
+ util_uri = uri_of(path.join(tmp, 'util.pyj'))
58
+ text = fs.readFileSync(path.join(tmp, 'main.pyj'), 'utf-8')
59
+ off = offset_of(text, 'helper(w)')
60
+ h = await lsp.hover(ctx, main_uri, text, off)
61
+ assrt.ok(h and h.contents.value.indexOf('helper') >= 0, 'hover should mention helper')
62
+
63
+ defs = await lsp.definition(ctx, main_uri, text, off)
64
+ assrt.ok(Array.isArray(defs) and defs.length >= 1, 'definition should return locations')
65
+ assrt.equal(defs[0].uri, util_uri, 'definition of helper should be in util.pyj')
66
+ assrt.equal(defs[0].range.start.line, 0, 'helper is defined on line 0 of util.pyj')
67
+ fs.rmSync(tmp, {'recursive': True})
68
+
69
+ async def test_cross_file_references():
70
+ tmp = write_workspace()
71
+ ctx = make_ctx(tmp)
72
+ main_uri = uri_of(path.join(tmp, 'main.pyj'))
73
+ util_uri = uri_of(path.join(tmp, 'util.pyj'))
74
+ text = fs.readFileSync(path.join(tmp, 'main.pyj'), 'utf-8')
75
+ off = offset_of(text, 'helper(w)')
76
+ refs = await lsp.references(ctx, main_uri, text, off, True)
77
+ uris = {}
78
+ for r in refs:
79
+ uris[r.uri] = True
80
+ assrt.ok(uris[main_uri], 'references should include occurrences in main.pyj')
81
+ assrt.ok(uris[util_uri], 'references should include the definition file util.pyj')
82
+ assrt.ok(refs.length >= 3, 'expected at least 3 references to helper across files')
83
+ fs.rmSync(tmp, {'recursive': True})
84
+
85
+ async def test_cross_file_rename():
86
+ tmp = write_workspace()
87
+ ctx = make_ctx(tmp)
88
+ main_uri = uri_of(path.join(tmp, 'main.pyj'))
89
+ util_uri = uri_of(path.join(tmp, 'util.pyj'))
90
+ text = fs.readFileSync(path.join(tmp, 'main.pyj'), 'utf-8')
91
+ off = offset_of(text, 'helper(w)')
92
+ we = await lsp.rename(ctx, main_uri, text, off, 'compute')
93
+ assrt.ok(we and we.changes, 'rename should return a workspace edit')
94
+ assrt.ok(we.changes[main_uri] and we.changes[main_uri].length >= 2, 'main.pyj should have >= 2 edits')
95
+ assrt.ok(we.changes[util_uri] and we.changes[util_uri].length >= 1, 'util.pyj should have the definition edit')
96
+ for e in we.changes[util_uri]:
97
+ assrt.equal(e.newText, 'compute', 'edit should insert the new name')
98
+ fs.rmSync(tmp, {'recursive': True})
99
+
100
+ async def test_invalid_rename():
101
+ tmp = write_workspace()
102
+ ctx = make_ctx(tmp)
103
+ main_uri = uri_of(path.join(tmp, 'main.pyj'))
104
+ text = fs.readFileSync(path.join(tmp, 'main.pyj'), 'utf-8')
105
+ off = offset_of(text, 'helper(w)')
106
+ threw = False
107
+ try:
108
+ await lsp.rename(ctx, main_uri, text, off, 'not a name')
109
+ except:
110
+ threw = True
111
+ assrt.ok(threw, 'renaming to an invalid identifier must be rejected')
112
+ fs.rmSync(tmp, {'recursive': True})
113
+
114
+ async def test_local_rename():
115
+ tmp = write_workspace()
116
+ ctx = make_ctx(tmp)
117
+ uri = uri_of(path.join(tmp, 'local.pyj'))
118
+ text = 'def f():\n total = 0\n total = total + 1\n return total\n'
119
+ we = await lsp.rename(ctx, uri, text, offset_of(text, 'return total') + 'return '.length, 'sum')
120
+ assrt.ok(we and we.changes and we.changes[uri], 'local rename should return edits')
121
+ assrt.equal(we.changes[uri].length, 4, 'total is used 4 times (1 def + 3 refs)')
122
+ fs.rmSync(tmp, {'recursive': True})
123
+
124
+ async def test_completion():
125
+ tmp = write_workspace()
126
+ ctx = make_ctx(tmp)
127
+ main_uri = uri_of(path.join(tmp, 'main.pyj'))
128
+ text = fs.readFileSync(path.join(tmp, 'main.pyj'), 'utf-8')
129
+ # After "util." we should be offered the module's exports.
130
+ dot_off = offset_of(text, 'util.Widget') + 'util.'.length
131
+ items = await lsp.completions(ctx, main_uri, text, dot_off)
132
+ labels = [it.label for it in items]
133
+ assrt.ok(labels.indexOf('Widget') >= 0, 'member completion should include Widget')
134
+ assrt.ok(labels.indexOf('helper') >= 0, 'member completion should include helper')
135
+
136
+ # Bare completion should include in-scope symbols, builtins and keywords.
137
+ bare = await lsp.completions(ctx, main_uri, text, offset_of(text, 'unused_var'))
138
+ blabels = [it.label for it in bare]
139
+ assrt.ok(blabels.indexOf('run') >= 0, 'completion should include the top-level function run')
140
+ assrt.ok(blabels.indexOf('print') >= 0, 'completion should include builtins')
141
+ fs.rmSync(tmp, {'recursive': True})
142
+
143
+ async def test_formatting():
144
+ ctx = lsp.create_server_context({'line_length': 80, 'preferred_quote': 'single'})
145
+ edits = lsp.format_document(ctx, 'x=1;y=2\n')
146
+ assrt.equal(edits.length, 1, 'formatting a mis-spaced document should yield one edit')
147
+ assrt.equal(edits[0].newText, 'x = 1; y = 2\n', 'formatter should normalize spacing')
148
+ # An already-formatted document yields no edits.
149
+ assrt.equal(lsp.format_document(ctx, 'x = 1\n').length, 0, 'well formatted input needs no edits')
150
+
151
+ async def test_error_recovery_still_analyzes():
152
+ tmp = write_workspace()
153
+ ctx = make_ctx(tmp)
154
+ uri = uri_of(path.join(tmp, 'broke.pyj'))
155
+ # alpha() has a syntax error but beta() must still be analyzable.
156
+ text = 'def alpha():\n x = \n return x\n\ndef beta():\n y = 1\n return y + y\n'
157
+ diags = await lsp.compute_diagnostics(ctx, uri, text)
158
+ assrt.ok(codes(diags)['syntax-err'], 'a syntax error should be reported')
159
+ off = offset_of(text, 'return y + y') + 'return '.length
160
+ h = await lsp.hover(ctx, uri, text, off)
161
+ assrt.ok(h and h.contents.value.indexOf('y') >= 0, 'hover must work in the healthy part of a broken file')
162
+ fs.rmSync(tmp, {'recursive': True})
163
+
164
+ async def test_parser_recovery_flag():
165
+ # The recover_errors parser option must never regress the normal path: a
166
+ # broken file throws without it and yields recovered_errors with it.
167
+ code = 'def good():\n return 1\n\ndef bad(:\n pass\n\nx = 2\n'
168
+ threw = False
169
+ try:
170
+ await RapydScript.parse(code, {'filename': 'r.pyj', 'basedir': test_path, 'libdir': libdir, 'for_linting': True})
171
+ except:
172
+ threw = True
173
+ assrt.ok(threw, 'normal parse must still throw on syntax errors (no regression)')
174
+
175
+ top = await RapydScript.parse(code, {'filename': 'r.pyj', 'basedir': test_path, 'libdir': libdir, 'for_linting': True, 'recover_errors': True})
176
+ assrt.ok(top, 'recover_errors parse should return a toplevel')
177
+ assrt.ok(top.recovered_errors and top.recovered_errors.length >= 1, 'recovered_errors should be populated')
178
+
179
+ async def test_configuration_change():
180
+ ctx = lsp.create_server_context({'line_length': 80, 'preferred_quote': 'single', 'import_dirs': []})
181
+
182
+ # Basic updates.
183
+ lsp.apply_configuration(ctx, {'lineLength': 120, 'preferredQuote': 'double', 'importPath': '/tmp/mylibs'})
184
+ assrt.equal(ctx.line_length, 120, 'lineLength should update line_length')
185
+ assrt.equal(ctx.preferred_quote, 'double', 'preferredQuote should update preferred_quote')
186
+ assrt.ok(ctx.import_dirs.length > 0, 'string importPath should populate import_dirs')
187
+
188
+ # Array-form importPath.
189
+ lsp.apply_configuration(ctx, {'importPath': ['/tmp/lib1', '/tmp/lib2']})
190
+ assrt.equal(ctx.import_dirs.length, 2, 'array importPath should yield 2 entries')
191
+
192
+ # Invalid values must be silently ignored.
193
+ lsp.apply_configuration(ctx, {'lineLength': 'bogus', 'preferredQuote': 'wrong'})
194
+ assrt.equal(ctx.line_length, 120, 'bogus lineLength must be ignored')
195
+ assrt.equal(ctx.preferred_quote, 'double', 'invalid preferredQuote must be ignored')
196
+
197
+ # Empty/absent settings must be a no-op.
198
+ lsp.apply_configuration(ctx, {})
199
+ assrt.equal(ctx.line_length, 120, 'empty settings must not reset line_length')
200
+
201
+ # Numeric lineLength (number type, not string).
202
+ lsp.apply_configuration(ctx, {'lineLength': 100})
203
+ assrt.equal(ctx.line_length, 100, 'numeric lineLength should be accepted')
204
+
205
+ # Empty importPath string clears the dirs.
206
+ lsp.apply_configuration(ctx, {'importPath': ''})
207
+ assrt.equal(ctx.import_dirs.length, 0, 'empty importPath string should clear import_dirs')
208
+
209
+ async def run_tests():
210
+ await test_diagnostics()
211
+ await test_hover_and_definition()
212
+ await test_cross_file_references()
213
+ await test_cross_file_rename()
214
+ await test_invalid_rename()
215
+ await test_local_rename()
216
+ await test_completion()
217
+ await test_formatting()
218
+ await test_error_recovery_still_analyzes()
219
+ await test_parser_recovery_flag()
220
+ await test_configuration_change()
221
+
222
+ __test_async_done__ = run_tests()
package/test/repl.pyj CHANGED
@@ -1,4 +1,4 @@
1
- # globals: compiler_dir
1
+ # globals: compiler_dir, rs_repl
2
2
  stdout = []
3
3
  def clear():
4
4
  stdout.length = 0
@@ -40,48 +40,49 @@ class FakeReadline:
40
40
  stdout.push(self._prompt)
41
41
 
42
42
  def send_line(self, text):
43
- self.listeners['line'](text)
43
+ return self.listeners['line'](text)
44
44
 
45
- repl = require('./repl')
45
+ repl = rs_repl
46
46
  rl = FakeReadline()
47
47
 
48
- send_line = rl.send_line.bind(rl)
49
-
50
- def send_text(text):
51
- for line in text.split('\n'):
52
- send_line(line)
53
-
54
- def check(text, output):
55
- send_text(text)
56
- eq(output, stdout[0])
57
- clear()
58
-
59
- def check_in(text, output):
60
- send_text(text)
61
- assrt.ok(output in stdout, output + ' not in ' + stdout)
62
- clear()
63
-
64
- def check_not_in(text, output):
65
- send_text(text)
66
- assrt.ok(output not in stdout)
67
- clear()
68
-
69
48
  readline = {
70
49
  'createInterface': def(options):
71
50
  rl.completer = options.completer
72
51
  return rl
73
52
  }
74
- repl({'lib_path':compiler_dir, 'console':FakeConsole(), 'readline':readline, 'terminal':False, 'show_js':False, 'histfile':False})
75
- eq = assrt.equal
76
- eq('>>> ', stdout[-1])
77
- clear()
78
- check('1', '1')
79
- check_in('if 1:\n 2\n \n ', '2')
80
- check_not_in('if 1:\n 2\n ', '2')
81
- check_in('1 +\n1\n\n', '2')
82
- check('max(1, 2)', '2')
83
- send_text(
84
- '''
53
+
54
+ async def run_tests():
55
+ await repl({'lib_path':compiler_dir, 'console':FakeConsole(), 'readline':readline, 'terminal':False, 'show_js':False, 'histfile':False})
56
+ eq = assrt.equal
57
+
58
+ async def send_text(text):
59
+ for line in text.split('\n'):
60
+ await rl.send_line(line)
61
+
62
+ async def check(text, output):
63
+ await send_text(text)
64
+ eq(output, stdout[0])
65
+ clear()
66
+
67
+ async def check_in(text, output):
68
+ await send_text(text)
69
+ assrt.ok(output in stdout, output + ' not in ' + stdout)
70
+ clear()
71
+
72
+ async def check_not_in(text, output):
73
+ await send_text(text)
74
+ assrt.ok(output not in stdout)
75
+ clear()
76
+
77
+ eq('>>> ', stdout[-1])
78
+ clear()
79
+ await check('1', '1')
80
+ await check_in('if 1:\n 2\n \n ', '2')
81
+ await check_not_in('if 1:\n 2\n ', '2')
82
+ await check_in('1 +\n1\n\n', '2')
83
+ await check('max(1, 2)', '2')
84
+ await send_text(
85
+ '''
85
86
  class A:
86
87
 
87
88
  def __init__(self, a):
@@ -89,33 +90,37 @@ class A:
89
90
 
90
91
 
91
92
  ''')
92
- clear()
93
- check_in('b = A(1)\nb.a', '1')
94
- check_in('c = A(2)\nc.a', '2')
95
- send_text('from __python__ import dict_literals\nd={1:1}')
96
- check_in('isinstance(d, dict)', 'true')
97
- send_text('from __python__ import no_dict_literals\nd={1:1}')
98
- check_in('isinstance(d, dict)', 'false')
99
- # Test completions
100
- def completions(line):
101
- return rl.completer(line)[0]
102
-
103
- def check_completions():
104
- items = completions(arguments[0])
105
- for x in Array.prototype.slice.call(arguments, 1):
106
- assrt.ok(items and x in items, x + ' not in completions for: ' + arguments[0])
107
-
108
- check_completions('', 'return', 'A')
109
- check_completions('Array.', 'isArray', 'apply')
110
- send_text('x = ""\ny = []'), clear()
111
- check_completions('x.', 'substr', 'trim')
112
- check_completions('y.', 'concat', 'push')
113
- check_completions('x.sl', 'slice')
114
- send_text('y = {"x":1}'), clear()
115
- check_completions('y.', 'x')
116
-
117
- # Test docstrings
118
- clear()
119
- send_text('def ds():\n "xxx"\n\n')
120
- clear()
121
- check('ds.__doc__', "'xxx'")
93
+ clear()
94
+ await check_in('b = A(1)\nb.a', '1')
95
+ await check_in('c = A(2)\nc.a', '2')
96
+ await send_text('from __python__ import dict_literals\nd={1:1}')
97
+ await check_in('isinstance(d, dict)', 'true')
98
+ await send_text('from __python__ import no_dict_literals\nd={1:1}')
99
+ await check_in('isinstance(d, dict)', 'false')
100
+ # Test completions
101
+ def completions(line):
102
+ return rl.completer(line)[0]
103
+
104
+ def check_completions():
105
+ items = completions(arguments[0])
106
+ for x in Array.prototype.slice.call(arguments, 1):
107
+ assrt.ok(items and x in items, x + ' not in completions for: ' + arguments[0])
108
+
109
+ check_completions('', 'return', 'A')
110
+ check_completions('Array.', 'isArray', 'apply')
111
+ await send_text('x = ""\ny = []')
112
+ clear()
113
+ check_completions('x.', 'substr', 'trim')
114
+ check_completions('y.', 'concat', 'push')
115
+ check_completions('x.sl', 'slice')
116
+ await send_text('y = {"x":1}')
117
+ clear()
118
+ check_completions('y.', 'x')
119
+
120
+ # Test docstrings
121
+ clear()
122
+ await send_text('def ds():\n "xxx"\n\n')
123
+ clear()
124
+ await check('ds.__doc__', "'xxx'")
125
+
126
+ __test_async_done__ = run_tests()