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.
- package/CHANGELOG.md +28 -0
- package/README.md +80 -26
- package/bin/build.ts +117 -0
- package/bin/package.json +1 -0
- package/bin/rapydscript +65 -62
- package/build_wheels.py +133 -0
- package/editor-plugins/README.md +80 -0
- package/editor-plugins/nvim.lua +321 -0
- package/local-agent.md +28 -0
- package/package.json +4 -5
- package/publish.py +27 -17
- package/release/baselib-plain-pretty.js +448 -326
- package/release/baselib-plain-ugly.js +3 -3
- package/release/compiler.js +9027 -8139
- package/release/signatures.json +27 -27
- package/release/stdlib_modules.json +1 -0
- package/src/ast.pyj +428 -280
- package/src/baselib-builtins.pyj +49 -28
- package/src/baselib-containers.pyj +241 -226
- package/src/baselib-errors.pyj +8 -1
- package/src/baselib-internal.pyj +38 -23
- package/src/baselib-itertools.pyj +13 -7
- package/src/baselib-str.pyj +59 -84
- package/src/compiler.pyj +4 -4
- package/src/errors.pyj +3 -4
- package/src/lib/aes.pyj +46 -32
- package/src/lib/elementmaker.pyj +11 -9
- package/src/lib/encodings.pyj +15 -5
- package/src/lib/gettext.pyj +9 -4
- package/src/lib/math.pyj +106 -45
- package/src/lib/operator.pyj +10 -10
- package/src/lib/pythonize.pyj +2 -1
- package/src/lib/random.pyj +28 -21
- package/src/lib/re.pyj +492 -19
- package/src/lib/traceback.pyj +171 -29
- package/src/lib/uuid.pyj +2 -2
- package/src/output/classes.pyj +28 -27
- package/src/output/codegen.pyj +87 -83
- package/src/output/comments.pyj +8 -8
- package/src/output/exceptions.pyj +20 -21
- package/src/output/functions.pyj +66 -64
- package/src/output/literals.pyj +24 -23
- package/src/output/loops.pyj +84 -142
- package/src/output/modules.pyj +55 -68
- package/src/output/operators.pyj +40 -29
- package/src/output/statements.pyj +21 -15
- package/src/output/stream.pyj +82 -56
- package/src/output/utils.pyj +12 -8
- package/src/parse.pyj +971 -620
- package/src/string_interpolation.pyj +5 -3
- package/src/tokenizer.pyj +188 -148
- package/src/utils.pyj +32 -15
- package/test/_treeshake_mod.pyj +30 -0
- package/test/_treeshake_side_effect.pyj +9 -0
- package/test/ast_serialization.pyj +392 -0
- package/test/async.pyj +95 -0
- package/test/baselib.pyj +1 -2
- package/test/embedded_compiler.pyj +57 -0
- package/test/fmt.pyj +291 -0
- package/test/generic.pyj +16 -3
- package/test/imports.pyj +8 -6
- package/test/internationalization.pyj +38 -37
- package/test/lint.pyj +120 -117
- package/test/lsp.pyj +257 -0
- package/test/repl.pyj +70 -65
- package/test/sourcemaps.pyj +315 -0
- package/test/starargs.pyj +46 -39
- package/test/str.pyj +8 -0
- package/test/traceback.pyj +263 -0
- package/test/treeshaking.pyj +181 -0
- package/test/web_repl_export.pyj +88 -0
- package/tools/ast_serialize.mjs +557 -0
- package/tools/cli.mjs +531 -0
- package/tools/compile.mjs +205 -0
- package/tools/compiler.mjs +159 -0
- package/tools/{completer.js → completer.mjs} +6 -6
- package/tools/{embedded_compiler.js → embedded_compiler.mjs} +17 -13
- package/tools/export.js +109 -56
- package/tools/fmt.mjs +975 -0
- package/tools/{gettext.js → gettext.mjs} +46 -53
- package/tools/ini.mjs +175 -0
- package/tools/{lint.js → lint.mjs} +78 -55
- package/tools/lsp.mjs +964 -0
- package/tools/lsp_protocol.mjs +259 -0
- package/tools/lsp_symbols.mjs +418 -0
- package/tools/{msgfmt.js → msgfmt.mjs} +18 -18
- package/tools/{repl.js → repl.mjs} +56 -42
- package/tools/{self.js → self.mjs} +71 -46
- package/tools/sourcemap.mjs +123 -0
- package/tools/test.mjs +252 -0
- package/tools/treeshake.mjs +400 -0
- package/tools/{utils.js → utils.mjs} +26 -23
- package/tools/{web_repl.js → web_repl.mjs} +15 -13
- package/tools/web_repl_export.mjs +97 -0
- package/tree-sitter/README.md +101 -0
- package/tree-sitter/grammar.js +992 -0
- package/tree-sitter/package.json +43 -0
- package/tree-sitter/queries/highlights.scm +232 -0
- package/tree-sitter/queries/indents.scm +64 -0
- package/tree-sitter/queries/injections.scm +14 -0
- package/tree-sitter/queries/locals.scm +108 -0
- package/tree-sitter/src/grammar.json +4976 -0
- package/tree-sitter/src/node-types.json +2901 -0
- package/tree-sitter/src/parser.c +196465 -0
- package/tree-sitter/src/scanner.c +294 -0
- package/tree-sitter/src/tree_sitter/alloc.h +54 -0
- package/tree-sitter/src/tree_sitter/array.h +330 -0
- package/tree-sitter/src/tree_sitter/parser.h +286 -0
- package/tree-sitter/test/corpus/chaining.txt +99 -0
- package/tree-sitter/test/corpus/classes.txt +147 -0
- package/tree-sitter/test/corpus/comprehensions.txt +155 -0
- package/tree-sitter/test/corpus/containers.txt +242 -0
- package/tree-sitter/test/corpus/control_flow.txt +361 -0
- package/tree-sitter/test/corpus/decorators.txt +64 -0
- package/tree-sitter/test/corpus/existential.txt +102 -0
- package/tree-sitter/test/corpus/functions.txt +293 -0
- package/tree-sitter/test/corpus/imports.txt +117 -0
- package/tree-sitter/test/corpus/literals.txt +135 -0
- package/tree-sitter/test/corpus/operators.txt +296 -0
- package/tree-sitter/test/corpus/statements.txt +189 -0
- package/tree-sitter/test/corpus/strings.txt +90 -0
- package/tree-sitter/test/corpus/subscripts.txt +227 -0
- package/tree-sitter/tree-sitter.json +36 -0
- package/web-repl/env.js +35 -23
- package/web-repl/main.js +8 -8
- package/bin/export +0 -75
- package/bin/web-repl-export +0 -102
- package/tools/cli.js +0 -523
- package/tools/compile.js +0 -184
- package/tools/compiler.js +0 -84
- package/tools/ini.js +0 -65
- package/tools/test.js +0 -110
package/test/lint.pyj
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
# License: BSD
|
|
3
3
|
# Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
|
4
4
|
|
|
5
|
-
linter = require('../tools/lint.
|
|
5
|
+
linter = require('../tools/lint.mjs')
|
|
6
6
|
|
|
7
|
-
def lint(code):
|
|
8
|
-
return linter.lint_code(code, {'filename':'<test>', 'report':def():
|
|
7
|
+
async def lint(code):
|
|
8
|
+
return await linter.lint_code(code, {'filename':'<test>', 'report':def():
|
|
9
9
|
pass
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
def err_test(code, ident, line, name, other_line):
|
|
13
|
-
msgs = lint(code)
|
|
12
|
+
async def err_test(code, ident, line, name, other_line):
|
|
13
|
+
msgs = await lint(code)
|
|
14
14
|
assrt.ok(len(msgs) > 0, 'failed to find error in: ' + code)
|
|
15
15
|
assrt.equal(len(msgs), 1, 'found ' + (len(msgs) - 1) + ' extra errors in: ' + code)
|
|
16
16
|
assrt.equal(msgs[0].ident, ident, 'incorrect ident: ' + msgs[0].ident + ' for: ' + code)
|
|
@@ -21,110 +21,111 @@ def err_test(code, ident, line, name, other_line):
|
|
|
21
21
|
if other_line != undefined:
|
|
22
22
|
assrt.equal(msgs[0].other_line, other_line, 'incorrect other_line: ' + msgs[0].other_line + ' for: ' + code)
|
|
23
23
|
|
|
24
|
-
def ok_test(code):
|
|
25
|
-
msgs = lint(code)
|
|
24
|
+
async def ok_test(code):
|
|
25
|
+
msgs = await lint(code)
|
|
26
26
|
assrt.equal(msgs.length, 0, 'Got unexpected msg: ' + (msgs[0] or {}).message + ' for code: ' + code)
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
err_test('import foo
|
|
31
|
-
err_test('import foo
|
|
32
|
-
err_test('
|
|
33
|
-
err_test('from x import foo
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
ok_test('def f(a
|
|
39
|
-
ok_test('def f(
|
|
40
|
-
ok_test('def f(
|
|
41
|
-
ok_test('def f(a):\n return
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
ok_test('def f():\n a
|
|
52
|
-
ok_test('
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
err_test('def f():\n
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
err_test('
|
|
66
|
-
err_test('
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ok_test('[
|
|
71
|
-
ok_test('[1 for x in [1,2]]')
|
|
72
|
-
ok_test('
|
|
73
|
-
ok_test('def f():\n l=[1,2];
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
ok_test('def f():\n for x in "":\n
|
|
79
|
-
ok_test('def f():\n for x
|
|
80
|
-
ok_test('
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
err_test('a=1
|
|
86
|
-
|
|
87
|
-
ok_test(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
ok_test('f() # noqa
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
err_test('if 1:\n
|
|
101
|
-
err_test('
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
ok_test('def a():\n x = 1\n def b():\n nonlocal x\n x\n
|
|
114
|
-
ok_test('def a():\n x = 1\n def ():\n nonlocal x\n x =
|
|
115
|
-
ok_test('
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
28
|
+
async def run_tests():
|
|
29
|
+
# Imports
|
|
30
|
+
await err_test('import foo', 'unused-import', 1, 'foo')
|
|
31
|
+
await err_test('import foo.boo', 'unused-import', 1, 'foo')
|
|
32
|
+
await err_test('import foo as boo', 'unused-import', 1, 'boo')
|
|
33
|
+
await err_test('from x import foo', 'unused-import', 1, 'foo')
|
|
34
|
+
await err_test('from x import foo as boo', 'unused-import', 1, 'boo')
|
|
35
|
+
await ok_test('import foo\nfoo')
|
|
36
|
+
|
|
37
|
+
# Function arguments
|
|
38
|
+
await ok_test('def f(a):\n return a')
|
|
39
|
+
await ok_test('def f(a=1):\n return a')
|
|
40
|
+
await ok_test('def f(*a):\n return a')
|
|
41
|
+
await ok_test('def f(**a):\n return a')
|
|
42
|
+
await ok_test('def f(a):\n return 1')
|
|
43
|
+
|
|
44
|
+
# Extended slices
|
|
45
|
+
await ok_test('a = []; a[::2], a[1:-1]')
|
|
46
|
+
|
|
47
|
+
# Top level unused ignored
|
|
48
|
+
await ok_test('a=1\ndef f():\n pass')
|
|
49
|
+
|
|
50
|
+
# Destructuring assignment
|
|
51
|
+
await ok_test('def f():\n a, b = 1, 2; a, b')
|
|
52
|
+
await ok_test('def f():\n a = 1; b, c = a, 2; return b, c')
|
|
53
|
+
await ok_test('for x, (y, z) in [ [1, [2, 3]] ]:\n x + y + z')
|
|
54
|
+
await err_test('def f():\n a, b = 1, 1; return a', 'unused-local', 2, 'b')
|
|
55
|
+
|
|
56
|
+
# Compound assignment
|
|
57
|
+
await err_test('a += 1', 'undef', 1, 'a')
|
|
58
|
+
await ok_test('def f():\n a = 1; a += 1')
|
|
59
|
+
|
|
60
|
+
# Unused bindings
|
|
61
|
+
await err_test('def f():\n a=1', 'unused-local', 2, 'a')
|
|
62
|
+
await err_test('def f():\n def a():\n pass', 'unused-local', 2, 'a')
|
|
63
|
+
|
|
64
|
+
# Undefined references
|
|
65
|
+
await err_test('f()', 'undef', 1, 'f')
|
|
66
|
+
await err_test('a', 'undef', 1, 'a')
|
|
67
|
+
await err_test('def f():\n a=1; return a\na', 'undef', 3, 'a')
|
|
68
|
+
|
|
69
|
+
# Comprehensions
|
|
70
|
+
await ok_test('[x for x in [1,2]]')
|
|
71
|
+
await ok_test('[1 for x in [1,2] if x]')
|
|
72
|
+
await ok_test('[1 for x in [1,2]]')
|
|
73
|
+
await ok_test('def f():\n l=[1,2];[x for x in l]')
|
|
74
|
+
await ok_test('def f():\n l=[1,2];{x:True for x in l}')
|
|
75
|
+
await err_test('def f():\n [x for x in [1,2]]; return x', 'undef', 2, 'x')
|
|
76
|
+
|
|
77
|
+
# Loops
|
|
78
|
+
await ok_test('def f():\n for x in "":\n pass\n return x')
|
|
79
|
+
await ok_test('def f():\n for x in "":\n x += 1\n')
|
|
80
|
+
await ok_test('def f():\n for x, y in "":\n pass\n return x, y')
|
|
81
|
+
await ok_test('for v"var i = 0; i < 1; i++":\n print(i)')
|
|
82
|
+
await err_test('def f():\n a = 1\n for a in "":\n a', 'loop-shadowed', 3, 'a', 2)
|
|
83
|
+
|
|
84
|
+
# Semi-colons
|
|
85
|
+
await err_test('a=1;;a', 'extra-semicolon', 1, ';')
|
|
86
|
+
await err_test('a=1;', 'eol-semicolon', 1, ';')
|
|
87
|
+
await ok_test('a = """\nhello;\n"""')
|
|
88
|
+
await ok_test("a = '''\nhello;\n'''")
|
|
89
|
+
|
|
90
|
+
# Builtins
|
|
91
|
+
for k in 'String Symbol this self window Map arguments print len range dir undefined'.split(' '):
|
|
92
|
+
await ok_test(k)
|
|
93
|
+
|
|
94
|
+
# noqa
|
|
95
|
+
await ok_test('f() # noqa')
|
|
96
|
+
await ok_test('f() # noqa:undef')
|
|
97
|
+
await err_test('f() # noqa:xxx', 'undef')
|
|
98
|
+
|
|
99
|
+
# Named func in branch
|
|
100
|
+
await err_test('if 1:\n def f():\n pass', 'func-in-branch', 2, 'f')
|
|
101
|
+
await err_test('if 1:\n pass\nelse:\n def f():\n pass', 'func-in-branch', 4, 'f')
|
|
102
|
+
await err_test('try:\n def f():\n pass\nexcept:\n pass', 'func-in-branch', 2, 'f')
|
|
103
|
+
await ok_test('if 1:\n a = def():\n pass')
|
|
104
|
+
|
|
105
|
+
# Syntax errors
|
|
106
|
+
await err_test('def f(:\n pass', 'syntax-err', 1)
|
|
107
|
+
|
|
108
|
+
# Functions
|
|
109
|
+
await ok_test('def f():\n pass\nf()')
|
|
110
|
+
|
|
111
|
+
# Non-locals
|
|
112
|
+
await err_test('def a():\n x = 1\n def b():\n nonlocal x\n b()', 'unused-local', 2, 'x')
|
|
113
|
+
await ok_test('def a():\n x = 1\n def b():\n nonlocal x\n x\n b()')
|
|
114
|
+
await ok_test('def a():\n x = 1\n def b():\n nonlocal x\n x\n x = 2\n b()')
|
|
115
|
+
await ok_test('def a():\n x = 1\n def ():\n nonlocal x\n x = 1\n x')
|
|
116
|
+
await ok_test('nonlocal a\na()')
|
|
117
|
+
|
|
118
|
+
# Define after use
|
|
119
|
+
await err_test('def g():\n f()\n f()\n def f():\n pass', 'def-after-use', 3, 'f')
|
|
120
|
+
|
|
121
|
+
# Decorators
|
|
122
|
+
await ok_test('from x import y\n@y\ndef f():\n pass')
|
|
123
|
+
|
|
124
|
+
# try/except
|
|
125
|
+
await ok_test('try:\n 1\nexcept Error as e:\n e')
|
|
126
|
+
|
|
127
|
+
# Classes
|
|
128
|
+
await ok_test('''
|
|
128
129
|
class A:
|
|
129
130
|
|
|
130
131
|
h = j = 1
|
|
@@ -146,21 +147,23 @@ class B(A):
|
|
|
146
147
|
def __init__(self):
|
|
147
148
|
A.__init__(self, 'b')
|
|
148
149
|
''')
|
|
149
|
-
err_test('class A:\n def a(self):\n a()', 'undef', 3, 'a')
|
|
150
|
-
err_test('class A:\n def a(self):\n pass\n def a(self):\n pass', 'dup-method', 4, 'a')
|
|
150
|
+
await err_test('class A:\n def a(self):\n a()', 'undef', 3, 'a')
|
|
151
|
+
await err_test('class A:\n def a(self):\n pass\n def a(self):\n pass', 'dup-method', 4, 'a')
|
|
151
152
|
|
|
152
|
-
# Object literals
|
|
153
|
-
err_test('{"a":1, "a":2}', 'dup-key', 1, 'a')
|
|
153
|
+
# Object literals
|
|
154
|
+
await err_test('{"a":1, "a":2}', 'dup-key', 1, 'a')
|
|
154
155
|
|
|
155
|
-
# keyword arg values
|
|
156
|
-
ok_test('def f():\n a=1\n f(b=a)')
|
|
157
|
-
ok_test('def f():\n a={}\n f(**a)')
|
|
156
|
+
# keyword arg values
|
|
157
|
+
await ok_test('def f():\n a=1\n f(b=a)')
|
|
158
|
+
await ok_test('def f():\n a={}\n f(**a)')
|
|
158
159
|
|
|
159
|
-
# with statement
|
|
160
|
-
ok_test('''
|
|
160
|
+
# with statement
|
|
161
|
+
await ok_test('''
|
|
161
162
|
def f():
|
|
162
163
|
a = 1
|
|
163
164
|
with a as b:
|
|
164
165
|
b()
|
|
165
166
|
''')
|
|
166
|
-
err_test('with a:\n pass', 'undef', '1', 'a')
|
|
167
|
+
await err_test('with a:\n pass', 'undef', '1', 'a')
|
|
168
|
+
|
|
169
|
+
__test_async_done__ = run_tests()
|
package/test/lsp.pyj
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
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 test_organize_imports():
|
|
210
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rs-lsp-org-'))
|
|
211
|
+
ctx = lsp.create_server_context({
|
|
212
|
+
'import_dirs': [tmp], 'libdir': libdir, 'workspace_roots': [tmp],
|
|
213
|
+
'line_length': 80, 'preferred_quote': 'single',
|
|
214
|
+
})
|
|
215
|
+
# Unorganized: stdlib after other -> edits are produced.
|
|
216
|
+
unorganized = 'import my_module\nimport math\n\nx = 1\n'
|
|
217
|
+
edits = lsp.organize_imports_document(ctx, unorganized)
|
|
218
|
+
assrt.ok(Array.isArray(edits) and edits.length > 0,
|
|
219
|
+
'organize_imports_document must return edits for unorganized imports')
|
|
220
|
+
assrt.ok(edits[0].newText.indexOf('import math') < edits[0].newText.indexOf('import my_module'),
|
|
221
|
+
'stdlib import (math) must come before other import in organized output')
|
|
222
|
+
assrt.ok(edits[0].newText.indexOf('\n\n') >= 0,
|
|
223
|
+
'organized output must have a blank line between stdlib and other groups')
|
|
224
|
+
# from __python__ import ... goes first, before stdlib.
|
|
225
|
+
with_python = 'import math\nfrom __python__ import os\n\nx = 1\n'
|
|
226
|
+
edits_py = lsp.organize_imports_document(ctx, with_python)
|
|
227
|
+
assrt.ok(Array.isArray(edits_py) and edits_py.length > 0,
|
|
228
|
+
'organize_imports_document must return edits when __python__ import is out of place')
|
|
229
|
+
assrt.ok(edits_py[0].newText.indexOf('from __python__') < edits_py[0].newText.indexOf('import math'),
|
|
230
|
+
'__python__ group must come before stdlib group in organized output')
|
|
231
|
+
# Already organized: no edits.
|
|
232
|
+
organized = 'import math\n\nimport my_module\n\nx = 1\n'
|
|
233
|
+
edits2 = lsp.organize_imports_document(ctx, organized)
|
|
234
|
+
assrt.equal(edits2.length, 0,
|
|
235
|
+
'organize_imports_document must return no edits for already-organized imports')
|
|
236
|
+
# No imports: no edits.
|
|
237
|
+
no_imports = 'x = 1\ny = 2\n'
|
|
238
|
+
edits3 = lsp.organize_imports_document(ctx, no_imports)
|
|
239
|
+
assrt.equal(edits3.length, 0,
|
|
240
|
+
'organize_imports_document must return no edits when there are no imports')
|
|
241
|
+
fs.rmSync(tmp, {'recursive': True})
|
|
242
|
+
|
|
243
|
+
async def run_tests():
|
|
244
|
+
await test_diagnostics()
|
|
245
|
+
await test_hover_and_definition()
|
|
246
|
+
await test_cross_file_references()
|
|
247
|
+
await test_cross_file_rename()
|
|
248
|
+
await test_invalid_rename()
|
|
249
|
+
await test_local_rename()
|
|
250
|
+
await test_completion()
|
|
251
|
+
await test_formatting()
|
|
252
|
+
await test_error_recovery_still_analyzes()
|
|
253
|
+
await test_parser_recovery_flag()
|
|
254
|
+
await test_configuration_change()
|
|
255
|
+
await test_organize_imports()
|
|
256
|
+
|
|
257
|
+
__test_async_done__ = run_tests()
|