rapydscript-ns 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.
- package/.agignore +1 -0
- package/.gitattributes +4 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/web-repl-page-deploy.yml +42 -0
- package/=template.pyj +5 -0
- package/CHANGELOG.md +456 -0
- package/CONTRIBUTORS +13 -0
- package/HACKING.md +103 -0
- package/LICENSE +24 -0
- package/README.md +2512 -0
- package/TODO.md +327 -0
- package/add-toc-to-readme +2 -0
- package/bin/export +75 -0
- package/bin/rapydscript +70 -0
- package/bin/web-repl-export +102 -0
- package/build +3 -0
- package/package.json +46 -0
- package/publish.py +37 -0
- package/release/baselib-plain-pretty.js +4370 -0
- package/release/baselib-plain-ugly.js +3 -0
- package/release/compiler.js +18394 -0
- package/release/signatures.json +31 -0
- package/session.vim +4 -0
- package/setup.cfg +2 -0
- package/src/ast.pyj +1356 -0
- package/src/baselib-builtins.pyj +279 -0
- package/src/baselib-containers.pyj +723 -0
- package/src/baselib-errors.pyj +37 -0
- package/src/baselib-internal.pyj +421 -0
- package/src/baselib-itertools.pyj +97 -0
- package/src/baselib-str.pyj +798 -0
- package/src/compiler.pyj +36 -0
- package/src/errors.pyj +30 -0
- package/src/lib/aes.pyj +646 -0
- package/src/lib/collections.pyj +695 -0
- package/src/lib/elementmaker.pyj +83 -0
- package/src/lib/encodings.pyj +126 -0
- package/src/lib/functools.pyj +148 -0
- package/src/lib/gettext.pyj +569 -0
- package/src/lib/itertools.pyj +580 -0
- package/src/lib/math.pyj +193 -0
- package/src/lib/numpy.pyj +2101 -0
- package/src/lib/operator.pyj +11 -0
- package/src/lib/pythonize.pyj +20 -0
- package/src/lib/random.pyj +118 -0
- package/src/lib/re.pyj +470 -0
- package/src/lib/traceback.pyj +63 -0
- package/src/lib/uuid.pyj +77 -0
- package/src/monaco-language-service/analyzer.js +526 -0
- package/src/monaco-language-service/builtins.js +543 -0
- package/src/monaco-language-service/completions.js +498 -0
- package/src/monaco-language-service/diagnostics.js +643 -0
- package/src/monaco-language-service/dts.js +550 -0
- package/src/monaco-language-service/hover.js +121 -0
- package/src/monaco-language-service/index.js +386 -0
- package/src/monaco-language-service/scope.js +162 -0
- package/src/monaco-language-service/signature.js +144 -0
- package/src/output/__init__.pyj +0 -0
- package/src/output/classes.pyj +296 -0
- package/src/output/codegen.pyj +492 -0
- package/src/output/comments.pyj +45 -0
- package/src/output/exceptions.pyj +105 -0
- package/src/output/functions.pyj +491 -0
- package/src/output/literals.pyj +109 -0
- package/src/output/loops.pyj +444 -0
- package/src/output/modules.pyj +329 -0
- package/src/output/operators.pyj +429 -0
- package/src/output/statements.pyj +463 -0
- package/src/output/stream.pyj +309 -0
- package/src/output/treeshake.pyj +182 -0
- package/src/output/utils.pyj +72 -0
- package/src/parse.pyj +3106 -0
- package/src/string_interpolation.pyj +72 -0
- package/src/tokenizer.pyj +702 -0
- package/src/unicode_aliases.pyj +576 -0
- package/src/utils.pyj +192 -0
- package/test/_import_one.pyj +37 -0
- package/test/_import_two/__init__.pyj +11 -0
- package/test/_import_two/level2/__init__.pyj +0 -0
- package/test/_import_two/level2/deep.pyj +4 -0
- package/test/_import_two/other.pyj +6 -0
- package/test/_import_two/sub.pyj +13 -0
- package/test/aes_vectors.pyj +421 -0
- package/test/annotations.pyj +80 -0
- package/test/baselib.pyj +319 -0
- package/test/classes.pyj +452 -0
- package/test/collections.pyj +152 -0
- package/test/decorators.pyj +77 -0
- package/test/dict_spread.pyj +76 -0
- package/test/docstrings.pyj +39 -0
- package/test/elementmaker_test.pyj +45 -0
- package/test/ellipsis.pyj +49 -0
- package/test/functions.pyj +151 -0
- package/test/generators.pyj +41 -0
- package/test/generic.pyj +370 -0
- package/test/imports.pyj +72 -0
- package/test/internationalization.pyj +73 -0
- package/test/lint.pyj +164 -0
- package/test/loops.pyj +85 -0
- package/test/numpy.pyj +734 -0
- package/test/omit_function_metadata.pyj +20 -0
- package/test/regexp.pyj +55 -0
- package/test/repl.pyj +121 -0
- package/test/scoped_flags.pyj +76 -0
- package/test/starargs.pyj +506 -0
- package/test/starred_assign.pyj +104 -0
- package/test/str.pyj +198 -0
- package/test/subscript_tuple.pyj +53 -0
- package/test/unit/fixtures/fibonacci_expected.js +46 -0
- package/test/unit/index.js +2989 -0
- package/test/unit/language-service-builtins.js +815 -0
- package/test/unit/language-service-completions.js +1067 -0
- package/test/unit/language-service-dts.js +543 -0
- package/test/unit/language-service-hover.js +455 -0
- package/test/unit/language-service-scope.js +833 -0
- package/test/unit/language-service-signature.js +458 -0
- package/test/unit/language-service.js +705 -0
- package/test/unit/run-language-service.js +41 -0
- package/test/unit/web-repl.js +484 -0
- package/tools/build-language-service.js +190 -0
- package/tools/cli.js +547 -0
- package/tools/compile.js +219 -0
- package/tools/compiler.js +108 -0
- package/tools/completer.js +131 -0
- package/tools/embedded_compiler.js +251 -0
- package/tools/export.js +316 -0
- package/tools/gettext.js +185 -0
- package/tools/ini.js +65 -0
- package/tools/lint.js +705 -0
- package/tools/msgfmt.js +187 -0
- package/tools/repl.js +223 -0
- package/tools/self.js +162 -0
- package/tools/test.js +118 -0
- package/tools/utils.js +128 -0
- package/tools/web_repl.js +95 -0
- package/try +41 -0
- package/web-repl/env.js +74 -0
- package/web-repl/index.html +163 -0
- package/web-repl/language-service.js +4084 -0
- package/web-repl/main.js +254 -0
- package/web-repl/prism.css +139 -0
- package/web-repl/prism.js +113 -0
- package/web-repl/rapydscript.js +435 -0
- package/web-repl/sha1.js +25 -0
|
@@ -0,0 +1,1067 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* test/unit/language-service-completions.js
|
|
3
|
+
*
|
|
4
|
+
* Unit tests for src/monaco-language-service/completions.js (Phase 3).
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node test/unit/language-service-completions.js # run all tests
|
|
8
|
+
* node test/unit/language-service-completions.js <test-name> # run a single test by name
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
var assert = require("assert");
|
|
13
|
+
var path = require("path");
|
|
14
|
+
var url = require("url");
|
|
15
|
+
var compiler_module = require("../../tools/compiler");
|
|
16
|
+
var utils = require("../../tools/utils");
|
|
17
|
+
var colored = utils.safe_colored;
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Mock Monaco CompletionItemKind (numeric values match Monaco's spec)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
var MockKind = {
|
|
24
|
+
Method: 0,
|
|
25
|
+
Function: 1,
|
|
26
|
+
Class: 5,
|
|
27
|
+
Variable: 6,
|
|
28
|
+
Module: 8,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Mock Monaco position object
|
|
32
|
+
function pos(line, col) { return { lineNumber: line, column: col }; }
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Test helpers
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** Extract suggestion labels from a CompletionList */
|
|
39
|
+
function labels(list) {
|
|
40
|
+
return list.suggestions.map(function (s) { return s.label; });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Assert that list.suggestions contains an item with the given label */
|
|
44
|
+
function assert_has(list, label, msg) {
|
|
45
|
+
var found = list.suggestions.some(function (s) { return s.label === label; });
|
|
46
|
+
assert.ok(found, (msg || "") + ": expected label '" + label + "' in " + JSON.stringify(labels(list)));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Assert that list.suggestions does NOT contain an item with the given label */
|
|
50
|
+
function assert_missing(list, label, msg) {
|
|
51
|
+
var found = list.suggestions.some(function (s) { return s.label === label; });
|
|
52
|
+
assert.ok(!found, (msg || "") + ": did not expect label '" + label + "' in " + JSON.stringify(labels(list)));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Find a suggestion by label, or null */
|
|
56
|
+
function find_item(list, label) {
|
|
57
|
+
return list.suggestions.find(function (s) { return s.label === label; }) || null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Test definitions
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function make_tests(CompletionEngine, detect_context, SourceAnalyzer, DtsRegistry, RS) {
|
|
65
|
+
|
|
66
|
+
function make_engine(virtual_files, extra_builtins, dts_registry, builtins_mod, stdlib_files) {
|
|
67
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
68
|
+
var builtins_registry = null;
|
|
69
|
+
if (builtins_mod) {
|
|
70
|
+
builtins_registry = new builtins_mod.BuiltinsRegistry();
|
|
71
|
+
}
|
|
72
|
+
return new CompletionEngine(analyzer, {
|
|
73
|
+
virtualFiles: virtual_files || {},
|
|
74
|
+
stdlibFiles: stdlib_files || {},
|
|
75
|
+
builtinNames: extra_builtins || ['print', 'len', 'range'],
|
|
76
|
+
dtsRegistry: dts_registry || null,
|
|
77
|
+
builtinsRegistry: builtins_registry,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
var FUNCTOOLS_SRC = require('fs').readFileSync(
|
|
82
|
+
require('path').join(__dirname, '../../src/lib/functools.pyj'), 'utf-8'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
var COLLECTIONS_SRC = require('fs').readFileSync(
|
|
86
|
+
require('path').join(__dirname, '../../src/lib/collections.pyj'), 'utf-8'
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
var TESTS = [
|
|
90
|
+
|
|
91
|
+
// ── detect_context ────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
name: "ctx_identifier",
|
|
95
|
+
description: "detect_context returns identifier type for plain word prefix",
|
|
96
|
+
run: function () {
|
|
97
|
+
var ctx = detect_context("pri");
|
|
98
|
+
assert.strictEqual(ctx.type, 'identifier');
|
|
99
|
+
assert.strictEqual(ctx.prefix, 'pri');
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
{
|
|
104
|
+
name: "ctx_dot",
|
|
105
|
+
description: "detect_context returns dot type for 'obj.'",
|
|
106
|
+
run: function () {
|
|
107
|
+
var ctx = detect_context("my_obj.me");
|
|
108
|
+
assert.strictEqual(ctx.type, 'dot');
|
|
109
|
+
assert.strictEqual(ctx.objectName, 'my_obj');
|
|
110
|
+
assert.strictEqual(ctx.prefix, 'me');
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
{
|
|
115
|
+
name: "ctx_dot_empty_prefix",
|
|
116
|
+
description: "detect_context handles 'obj.' with no attribute prefix",
|
|
117
|
+
run: function () {
|
|
118
|
+
var ctx = detect_context("dog.");
|
|
119
|
+
assert.strictEqual(ctx.type, 'dot');
|
|
120
|
+
assert.strictEqual(ctx.objectName, 'dog');
|
|
121
|
+
assert.strictEqual(ctx.prefix, '');
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
{
|
|
126
|
+
name: "ctx_dot_multi_level",
|
|
127
|
+
description: "detect_context captures multi-level path like 'ns.hacknet.'",
|
|
128
|
+
run: function () {
|
|
129
|
+
var ctx = detect_context("ns.hacknet.");
|
|
130
|
+
assert.strictEqual(ctx.type, 'dot');
|
|
131
|
+
assert.strictEqual(ctx.objectName, 'ns.hacknet');
|
|
132
|
+
assert.strictEqual(ctx.prefix, '');
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
name: "ctx_dot_multi_level_with_prefix",
|
|
138
|
+
description: "detect_context captures multi-level path with partial attribute",
|
|
139
|
+
run: function () {
|
|
140
|
+
var ctx = detect_context("ns.hacknet.pur");
|
|
141
|
+
assert.strictEqual(ctx.type, 'dot');
|
|
142
|
+
assert.strictEqual(ctx.objectName, 'ns.hacknet');
|
|
143
|
+
assert.strictEqual(ctx.prefix, 'pur');
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
{
|
|
148
|
+
name: "ctx_from_import",
|
|
149
|
+
description: "detect_context returns from_import for 'from mod import p'",
|
|
150
|
+
run: function () {
|
|
151
|
+
var ctx = detect_context("from mymod import fo");
|
|
152
|
+
assert.strictEqual(ctx.type, 'from_import');
|
|
153
|
+
assert.strictEqual(ctx.moduleName, 'mymod');
|
|
154
|
+
assert.strictEqual(ctx.prefix, 'fo');
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
{
|
|
159
|
+
name: "ctx_import",
|
|
160
|
+
description: "detect_context returns import for 'import p'",
|
|
161
|
+
run: function () {
|
|
162
|
+
var ctx = detect_context("import my");
|
|
163
|
+
assert.strictEqual(ctx.type, 'import');
|
|
164
|
+
assert.strictEqual(ctx.prefix, 'my');
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
// ── Scope completions ─────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
{
|
|
171
|
+
name: "scope_local_vars",
|
|
172
|
+
description: "Local variable and function names appear in scope completions",
|
|
173
|
+
run: function () {
|
|
174
|
+
var engine = make_engine();
|
|
175
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
176
|
+
var scopeMap = analyzer.analyze([
|
|
177
|
+
"my_var = 42",
|
|
178
|
+
"def my_func():",
|
|
179
|
+
" return my_var",
|
|
180
|
+
].join("\n"), {});
|
|
181
|
+
|
|
182
|
+
var list = engine.getCompletions(scopeMap, pos(3, 1), "", MockKind);
|
|
183
|
+
assert_has(list, 'my_var', 'local variable');
|
|
184
|
+
assert_has(list, 'my_func', 'function name');
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
name: "scope_prefix_filter",
|
|
190
|
+
description: "Prefix filtering narrows results to matching symbols",
|
|
191
|
+
run: function () {
|
|
192
|
+
var engine = make_engine();
|
|
193
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
194
|
+
var scopeMap = analyzer.analyze([
|
|
195
|
+
"alpha = 1",
|
|
196
|
+
"beta = 2",
|
|
197
|
+
"alpha_two = 3",
|
|
198
|
+
].join("\n"), {});
|
|
199
|
+
|
|
200
|
+
var list = engine.getCompletions(scopeMap, pos(3, 4), "alp", MockKind);
|
|
201
|
+
assert_has(list, 'alpha', 'alpha matches');
|
|
202
|
+
assert_has(list, 'alpha_two', 'alpha_two matches');
|
|
203
|
+
assert_missing(list,'beta', 'beta does not match alp');
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
{
|
|
208
|
+
name: "scope_builtins_included",
|
|
209
|
+
description: "Built-in names appear in scope completions",
|
|
210
|
+
run: function () {
|
|
211
|
+
var engine = make_engine({}, ['print', 'len', 'range']);
|
|
212
|
+
var scopeMap = new (require(path.join(__dirname, '../../src/monaco-language-service/scope.js')).ScopeMap)();
|
|
213
|
+
var list = engine.getCompletions(scopeMap, pos(1, 1), "", MockKind);
|
|
214
|
+
assert_has(list, 'print', 'print builtin');
|
|
215
|
+
assert_has(list, 'len', 'len builtin');
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
{
|
|
220
|
+
name: "scope_sort_local_before_builtin",
|
|
221
|
+
description: "Local variables sort before builtins",
|
|
222
|
+
run: function () {
|
|
223
|
+
var engine = make_engine({}, ['print']);
|
|
224
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
225
|
+
var scopeMap = analyzer.analyze("x = 1\nprint(x)", {});
|
|
226
|
+
|
|
227
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "", MockKind);
|
|
228
|
+
var x_item = find_item(list, 'x');
|
|
229
|
+
var print_item = find_item(list, 'print');
|
|
230
|
+
assert.ok(x_item, 'x should be in list');
|
|
231
|
+
assert.ok(print_item, 'print should be in list');
|
|
232
|
+
// Module-level symbols start with '1_', builtins '2_'
|
|
233
|
+
assert.ok(x_item.sortText < print_item.sortText,
|
|
234
|
+
'x should sort before print: ' + x_item.sortText + ' vs ' + print_item.sortText);
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
{
|
|
239
|
+
name: "scope_null_scopemap_safe",
|
|
240
|
+
description: "getCompletions handles null ScopeMap without throwing",
|
|
241
|
+
run: function () {
|
|
242
|
+
var engine = make_engine();
|
|
243
|
+
var list = engine.getCompletions(null, pos(1, 1), "", MockKind);
|
|
244
|
+
assert.ok(Array.isArray(list.suggestions), 'suggestions should be an array');
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// ── Item structure ────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
{
|
|
251
|
+
name: "item_range_set",
|
|
252
|
+
description: "Completion items have a valid range covering the prefix",
|
|
253
|
+
run: function () {
|
|
254
|
+
var engine = make_engine();
|
|
255
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
256
|
+
var scopeMap = analyzer.analyze("my_var = 1", {});
|
|
257
|
+
|
|
258
|
+
// Cursor at col 6, prefix 'my_va' (5 chars)
|
|
259
|
+
var list = engine.getCompletions(scopeMap, pos(1, 6), "my_va", MockKind);
|
|
260
|
+
var item = find_item(list, 'my_var');
|
|
261
|
+
assert.ok(item, 'my_var should be in suggestions');
|
|
262
|
+
assert.strictEqual(item.range.startColumn, 1, 'startColumn should be col - prefix.length');
|
|
263
|
+
assert.strictEqual(item.range.endColumn, 6, 'endColumn should be col');
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
{
|
|
268
|
+
name: "item_function_detail",
|
|
269
|
+
description: "Function completion items show parameter list in detail",
|
|
270
|
+
run: function () {
|
|
271
|
+
var engine = make_engine();
|
|
272
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
273
|
+
var scopeMap = analyzer.analyze([
|
|
274
|
+
"def greet(name, *args):",
|
|
275
|
+
" return name",
|
|
276
|
+
].join("\n"), {});
|
|
277
|
+
|
|
278
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "", MockKind);
|
|
279
|
+
var item = find_item(list, 'greet');
|
|
280
|
+
assert.ok(item, 'greet should be in list');
|
|
281
|
+
assert.ok(item.detail.indexOf('name') !== -1, 'detail should include param name');
|
|
282
|
+
assert.ok(item.detail.indexOf('*args') !== -1, 'detail should include *args');
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
|
|
286
|
+
// ── Dot completions ───────────────────────────────────────────────
|
|
287
|
+
|
|
288
|
+
{
|
|
289
|
+
name: "dot_class_methods",
|
|
290
|
+
description: "Dot completion on a class name returns its methods",
|
|
291
|
+
run: function () {
|
|
292
|
+
var engine = make_engine();
|
|
293
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
294
|
+
// Source must parse cleanly — the "Dog." prefix is passed separately.
|
|
295
|
+
// The extra line ensures the module scope extends past line 6.
|
|
296
|
+
var scopeMap = analyzer.analyze([
|
|
297
|
+
"class Dog:",
|
|
298
|
+
" def bark(self):",
|
|
299
|
+
" return 'woof'",
|
|
300
|
+
" def fetch(self):",
|
|
301
|
+
" return 'ball'",
|
|
302
|
+
"result = Dog",
|
|
303
|
+
"pass", // extra statement so col 1 of line 6 is inside module scope
|
|
304
|
+
].join("\n"), {});
|
|
305
|
+
|
|
306
|
+
// Query at col 1 of line 6 — safely inside the module scope range
|
|
307
|
+
var list = engine.getCompletions(scopeMap, pos(6, 1), "Dog.", MockKind);
|
|
308
|
+
assert_has(list, 'bark', 'Dog.bark');
|
|
309
|
+
assert_has(list, 'fetch', 'Dog.fetch');
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
{
|
|
314
|
+
name: "dot_instance_inferred_class",
|
|
315
|
+
description: "Dot completion on x = ClassName() infers class members",
|
|
316
|
+
run: function () {
|
|
317
|
+
var engine = make_engine();
|
|
318
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
319
|
+
// Source must parse cleanly — no trailing incomplete expression.
|
|
320
|
+
var scopeMap = analyzer.analyze([
|
|
321
|
+
"class Cat:",
|
|
322
|
+
" def meow(self):",
|
|
323
|
+
" return 'mrrr'",
|
|
324
|
+
"my_cat = Cat()",
|
|
325
|
+
"x = my_cat",
|
|
326
|
+
"pass", // extra statement so line 5 col 1 is inside module scope
|
|
327
|
+
].join("\n"), {});
|
|
328
|
+
|
|
329
|
+
// Query at col 1 of line 5 — safely inside the module scope
|
|
330
|
+
var list = engine.getCompletions(scopeMap, pos(5, 1), "my_cat.", MockKind);
|
|
331
|
+
assert_has(list, 'meow', 'Cat.meow via inferred_class');
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
{
|
|
336
|
+
name: "dot_no_class_empty",
|
|
337
|
+
description: "Dot completion on unknown object returns empty list",
|
|
338
|
+
run: function () {
|
|
339
|
+
var engine = make_engine();
|
|
340
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
341
|
+
var scopeMap = analyzer.analyze("x = 1\ny = x\npass", {});
|
|
342
|
+
|
|
343
|
+
// x is a plain variable with no inferred_class; query at col 1 of line 2
|
|
344
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "x.", MockKind);
|
|
345
|
+
assert.strictEqual(list.suggestions.length, 0, 'unknown type → no dot suggestions');
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
|
|
349
|
+
// ── from X import completions ─────────────────────────────────────
|
|
350
|
+
|
|
351
|
+
{
|
|
352
|
+
name: "from_import_lists_exports",
|
|
353
|
+
description: "from X import shows top-level names from virtual file",
|
|
354
|
+
run: function () {
|
|
355
|
+
var vf = {
|
|
356
|
+
mymod: [
|
|
357
|
+
"def foo():",
|
|
358
|
+
" return 1",
|
|
359
|
+
"def bar():",
|
|
360
|
+
" return 2",
|
|
361
|
+
"BAZ = 42",
|
|
362
|
+
].join("\n"),
|
|
363
|
+
};
|
|
364
|
+
var engine = make_engine(vf);
|
|
365
|
+
var list = engine.getCompletions(null, pos(1, 22), "from mymod import ", MockKind);
|
|
366
|
+
assert_has(list, 'foo', 'foo from virtual file');
|
|
367
|
+
assert_has(list, 'bar', 'bar from virtual file');
|
|
368
|
+
assert_has(list, 'BAZ', 'BAZ from virtual file');
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
|
|
372
|
+
{
|
|
373
|
+
name: "from_import_prefix_filter",
|
|
374
|
+
description: "from X import with prefix filters results",
|
|
375
|
+
run: function () {
|
|
376
|
+
var vf = {
|
|
377
|
+
mymod: "def foo():\n return 1\ndef far():\n return 2\ndef bar():\n return 3",
|
|
378
|
+
};
|
|
379
|
+
var engine = make_engine(vf);
|
|
380
|
+
var list = engine.getCompletions(null, pos(1, 23), "from mymod import f", MockKind);
|
|
381
|
+
assert_has(list, 'foo', 'foo matches f');
|
|
382
|
+
assert_has(list, 'far', 'far matches f');
|
|
383
|
+
assert_missing(list,'bar', 'bar does not match f');
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
|
|
387
|
+
{
|
|
388
|
+
name: "from_import_unknown_module",
|
|
389
|
+
description: "from X import for unknown module returns empty list",
|
|
390
|
+
run: function () {
|
|
391
|
+
var engine = make_engine({});
|
|
392
|
+
var list = engine.getCompletions(null, pos(1, 25), "from unknown_mod import ", MockKind);
|
|
393
|
+
assert.strictEqual(list.suggestions.length, 0, 'unknown module → empty suggestions');
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
// ── import module name completions ────────────────────────────────
|
|
398
|
+
|
|
399
|
+
{
|
|
400
|
+
name: "import_module_names",
|
|
401
|
+
description: "import shows available virtual module names",
|
|
402
|
+
run: function () {
|
|
403
|
+
var vf = { mymod: "x = 1", othermod: "y = 2" };
|
|
404
|
+
var engine = make_engine(vf);
|
|
405
|
+
var list = engine.getCompletions(null, pos(1, 7), "import ", MockKind);
|
|
406
|
+
assert_has(list, 'mymod', 'mymod in import suggestions');
|
|
407
|
+
assert_has(list, 'othermod', 'othermod in import suggestions');
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
{
|
|
412
|
+
name: "import_prefix_filter",
|
|
413
|
+
description: "import with prefix filters module names",
|
|
414
|
+
run: function () {
|
|
415
|
+
var vf = { mymod: "x = 1", othermod: "y = 2" };
|
|
416
|
+
var engine = make_engine(vf);
|
|
417
|
+
var list = engine.getCompletions(null, pos(1, 9), "import my", MockKind);
|
|
418
|
+
assert_has(list, 'mymod', 'mymod matches "my"');
|
|
419
|
+
assert_missing(list,'othermod', 'othermod does not match "my"');
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
// ── DTS dot completions ───────────────────────────────────────────
|
|
424
|
+
|
|
425
|
+
{
|
|
426
|
+
name: "dot_dts_namespace_members",
|
|
427
|
+
description: "Dot completion on a DTS namespace returns its members",
|
|
428
|
+
run: function () {
|
|
429
|
+
var reg = new DtsRegistry();
|
|
430
|
+
reg.addDts("lib", [
|
|
431
|
+
"declare namespace Math {",
|
|
432
|
+
" function abs(x: number): number;",
|
|
433
|
+
" function sqrt(x: number): number;",
|
|
434
|
+
" const PI: number;",
|
|
435
|
+
"}",
|
|
436
|
+
].join("\n"));
|
|
437
|
+
var engine = make_engine({}, [], reg);
|
|
438
|
+
// null scopeMap — Math is a pure DTS global
|
|
439
|
+
var list = engine.getCompletions(null, pos(1, 5), "Math.", MockKind);
|
|
440
|
+
assert_has(list, 'abs', 'Math.abs from DTS');
|
|
441
|
+
assert_has(list, 'sqrt', 'Math.sqrt from DTS');
|
|
442
|
+
assert_has(list, 'PI', 'Math.PI from DTS');
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
|
|
446
|
+
{
|
|
447
|
+
name: "dot_dts_interface_members",
|
|
448
|
+
description: "Dot completion on a DTS interface returns its members",
|
|
449
|
+
run: function () {
|
|
450
|
+
var reg = new DtsRegistry();
|
|
451
|
+
reg.addDts("lib", [
|
|
452
|
+
"interface Console {",
|
|
453
|
+
" log(...data: any[]): void;",
|
|
454
|
+
" error(message?: any): void;",
|
|
455
|
+
" warn(message?: any): void;",
|
|
456
|
+
"}",
|
|
457
|
+
"declare var console: Console;",
|
|
458
|
+
].join("\n"));
|
|
459
|
+
var engine = make_engine({}, [], reg);
|
|
460
|
+
var list = engine.getCompletions(null, pos(1, 8), "console.", MockKind);
|
|
461
|
+
assert_has(list, 'log', 'console.log from DTS');
|
|
462
|
+
assert_has(list, 'error', 'console.error from DTS');
|
|
463
|
+
assert_has(list, 'warn', 'console.warn from DTS');
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
{
|
|
468
|
+
name: "dot_dts_prefix_filter",
|
|
469
|
+
description: "DTS dot completion respects prefix filter",
|
|
470
|
+
run: function () {
|
|
471
|
+
var reg = new DtsRegistry();
|
|
472
|
+
reg.addDts("lib", [
|
|
473
|
+
"declare namespace Math {",
|
|
474
|
+
" function abs(x: number): number;",
|
|
475
|
+
" function sqrt(x: number): number;",
|
|
476
|
+
" function sin(x: number): number;",
|
|
477
|
+
"}",
|
|
478
|
+
].join("\n"));
|
|
479
|
+
var engine = make_engine({}, [], reg);
|
|
480
|
+
var list = engine.getCompletions(null, pos(1, 6), "Math.s", MockKind);
|
|
481
|
+
assert_has(list, 'sqrt', 'sqrt matches s');
|
|
482
|
+
assert_has(list, 'sin', 'sin matches s');
|
|
483
|
+
assert_missing(list,'abs', 'abs does not match s');
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
|
|
487
|
+
{
|
|
488
|
+
name: "dot_dts_method_item_structure",
|
|
489
|
+
description: "DTS method completion item has method kind and detail",
|
|
490
|
+
run: function () {
|
|
491
|
+
var reg = new DtsRegistry();
|
|
492
|
+
reg.addDts("lib", [
|
|
493
|
+
"declare namespace JSON {",
|
|
494
|
+
" function stringify(value: any): string;",
|
|
495
|
+
"}",
|
|
496
|
+
].join("\n"));
|
|
497
|
+
var engine = make_engine({}, [], reg);
|
|
498
|
+
var list = engine.getCompletions(null, pos(1, 5), "JSON.", MockKind);
|
|
499
|
+
var item = find_item(list, 'stringify');
|
|
500
|
+
assert.ok(item, 'stringify should be in suggestions');
|
|
501
|
+
assert.strictEqual(item.kind, MockKind.Method, 'should have Method kind');
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
{
|
|
506
|
+
name: "dot_dts_no_registry",
|
|
507
|
+
description: "Dot completion without DTS registry still works (empty for unknown)",
|
|
508
|
+
run: function () {
|
|
509
|
+
var engine = make_engine({}, [], null);
|
|
510
|
+
var list = engine.getCompletions(null, pos(1, 5), "Math.", MockKind);
|
|
511
|
+
assert.strictEqual(list.suggestions.length, 0,
|
|
512
|
+
'no DTS registry → no Math members');
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
// ── Built-in type dot completions ─────────────────────────────────
|
|
517
|
+
|
|
518
|
+
{
|
|
519
|
+
name: "dot_list_literal_members",
|
|
520
|
+
description: "Dot completion on myArr = [] shows list members (length, append, push…)",
|
|
521
|
+
run: function () {
|
|
522
|
+
var engine = make_engine({}, [], null,
|
|
523
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
524
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
525
|
+
var scopeMap = analyzer.analyze([
|
|
526
|
+
"myArr = []",
|
|
527
|
+
"pass",
|
|
528
|
+
].join("\n"), {});
|
|
529
|
+
|
|
530
|
+
// Query on line 2 — position-based lookup finds myArr in module scope
|
|
531
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "myArr.", MockKind);
|
|
532
|
+
assert_has(list, 'length', 'list.length property');
|
|
533
|
+
assert_has(list, 'append', 'list.append method');
|
|
534
|
+
assert_has(list, 'push', 'list.push method');
|
|
535
|
+
assert_has(list, 'map', 'list.map method');
|
|
536
|
+
assert_has(list, 'filter', 'list.filter method');
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
{
|
|
541
|
+
name: "dot_str_literal_members",
|
|
542
|
+
description: "Dot completion on myStr = 'x' shows str members (length, upper, split…)",
|
|
543
|
+
run: function () {
|
|
544
|
+
var engine = make_engine({}, [], null,
|
|
545
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
546
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
547
|
+
var scopeMap = analyzer.analyze([
|
|
548
|
+
"myStr = 'hello'",
|
|
549
|
+
"pass",
|
|
550
|
+
].join("\n"), {});
|
|
551
|
+
|
|
552
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "myStr.", MockKind);
|
|
553
|
+
assert_has(list, 'length', 'str.length property');
|
|
554
|
+
assert_has(list, 'upper', 'str.upper method');
|
|
555
|
+
assert_has(list, 'split', 'str.split method');
|
|
556
|
+
assert_has(list, 'startswith', 'str.startswith method');
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
{
|
|
561
|
+
name: "dot_dict_literal_members",
|
|
562
|
+
description: "Dot completion on myObj = {} shows dict members (keys, values, get…)",
|
|
563
|
+
run: function () {
|
|
564
|
+
var engine = make_engine({}, [], null,
|
|
565
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
566
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
567
|
+
var scopeMap = analyzer.analyze([
|
|
568
|
+
"myObj = {}",
|
|
569
|
+
"pass",
|
|
570
|
+
].join("\n"), {});
|
|
571
|
+
|
|
572
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "myObj.", MockKind);
|
|
573
|
+
assert_has(list, 'keys', 'dict.keys method');
|
|
574
|
+
assert_has(list, 'values', 'dict.values method');
|
|
575
|
+
assert_has(list, 'get', 'dict.get method');
|
|
576
|
+
assert_has(list, 'update', 'dict.update method');
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
{
|
|
581
|
+
name: "dot_builtin_type_prefix_filter",
|
|
582
|
+
description: "Prefix filter narrows built-in type members (myArr.le → length only)",
|
|
583
|
+
run: function () {
|
|
584
|
+
var engine = make_engine({}, [], null,
|
|
585
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
586
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
587
|
+
var scopeMap = analyzer.analyze("myArr = []\npass", {});
|
|
588
|
+
|
|
589
|
+
var list = engine.getCompletions(scopeMap, pos(2, 3), "myArr.le", MockKind);
|
|
590
|
+
assert_has(list, 'length', 'length matches le');
|
|
591
|
+
assert_missing(list,'append', 'append does not match le');
|
|
592
|
+
assert_missing(list,'push', 'push does not match le');
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
|
|
596
|
+
{
|
|
597
|
+
name: "dot_builtin_type_wins_over_dts",
|
|
598
|
+
description: "Built-in type members take precedence over DTS when inferred_class is set",
|
|
599
|
+
run: function () {
|
|
600
|
+
// DTS has a 'myArr' namespace; but myArr is a list literal so type members win
|
|
601
|
+
var reg = new DtsRegistry();
|
|
602
|
+
reg.addDts("lib", "declare namespace myArr { function dts_only(): void; }");
|
|
603
|
+
var engine = make_engine({}, [], reg,
|
|
604
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
605
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
606
|
+
var scopeMap = analyzer.analyze("myArr = []\npass", {});
|
|
607
|
+
|
|
608
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "myArr.", MockKind);
|
|
609
|
+
assert_has(list, 'length', 'list.length present');
|
|
610
|
+
assert_missing(list,'dts_only', 'DTS member absent when inferred_class matches');
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
// ── Function-local variable dot completions (scope fallback) ──────
|
|
615
|
+
|
|
616
|
+
{
|
|
617
|
+
name: "dot_function_local_list",
|
|
618
|
+
description: "Dot completion on function-local myArr = [] shows list members",
|
|
619
|
+
run: function () {
|
|
620
|
+
var engine = make_engine({}, [], null,
|
|
621
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
622
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
623
|
+
// Parse includes the dot-access line so the function range covers it.
|
|
624
|
+
var scopeMap = analyzer.analyze([
|
|
625
|
+
"def main():",
|
|
626
|
+
" myArr = []",
|
|
627
|
+
" x = myArr",
|
|
628
|
+
].join("\n"), {});
|
|
629
|
+
|
|
630
|
+
// Query on line 3 (inside the function) — the function scope must be found
|
|
631
|
+
var list = engine.getCompletions(scopeMap, pos(3, 5), "myArr.", MockKind);
|
|
632
|
+
assert_has(list, 'length', 'list.length in function scope');
|
|
633
|
+
assert_has(list, 'append', 'list.append in function scope');
|
|
634
|
+
assert_has(list, 'push', 'list.push in function scope');
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
{
|
|
639
|
+
name: "dot_function_local_scope_fallback",
|
|
640
|
+
description: "Dot completion works when cursor is past the last parsed scope boundary",
|
|
641
|
+
run: function () {
|
|
642
|
+
var engine = make_engine({}, [], null,
|
|
643
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
644
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
645
|
+
// Simulate the debounce scenario: the scopeMap was built from code that
|
|
646
|
+
// ends at line 2, but completions are requested at line 3 (new line).
|
|
647
|
+
var scopeMap = analyzer.analyze([
|
|
648
|
+
"def main():",
|
|
649
|
+
" myArr = []",
|
|
650
|
+
].join("\n"), {});
|
|
651
|
+
|
|
652
|
+
// Line 3 col 7 is past the end of any parsed scope range.
|
|
653
|
+
// The fallback all-frames search must still find myArr.
|
|
654
|
+
var list = engine.getCompletions(scopeMap, pos(3, 7), "myArr.", MockKind);
|
|
655
|
+
assert_has(list, 'length', 'list.length via scope fallback');
|
|
656
|
+
assert_has(list, 'append', 'list.append via scope fallback');
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
|
|
660
|
+
{
|
|
661
|
+
name: "dot_function_local_str_fallback",
|
|
662
|
+
description: "Scope fallback finds str type for function-local string variable",
|
|
663
|
+
run: function () {
|
|
664
|
+
var engine = make_engine({}, [], null,
|
|
665
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
666
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
667
|
+
var scopeMap = analyzer.analyze([
|
|
668
|
+
"async def main():",
|
|
669
|
+
" myStr = 'hello'",
|
|
670
|
+
].join("\n"), {});
|
|
671
|
+
|
|
672
|
+
// Cursor past end of parsed scopes
|
|
673
|
+
var list = engine.getCompletions(scopeMap, pos(3, 7), "myStr.", MockKind);
|
|
674
|
+
assert_has(list, 'length', 'str.length via scope fallback');
|
|
675
|
+
assert_has(list, 'upper', 'str.upper via scope fallback');
|
|
676
|
+
assert_has(list, 'split', 'str.split via scope fallback');
|
|
677
|
+
},
|
|
678
|
+
},
|
|
679
|
+
|
|
680
|
+
{
|
|
681
|
+
name: "dot_dts_multi_level",
|
|
682
|
+
description: "Multi-level dot completion resolves type chain (e.g. ns.sub.member)",
|
|
683
|
+
run: function () {
|
|
684
|
+
var reg = new DtsRegistry();
|
|
685
|
+
reg.addDts("lib", [
|
|
686
|
+
"interface Hacknet {",
|
|
687
|
+
" purchaseNode(): number;",
|
|
688
|
+
" getNodeStats(index: number): object;",
|
|
689
|
+
"}",
|
|
690
|
+
"interface NS {",
|
|
691
|
+
" readonly hacknet: Hacknet;",
|
|
692
|
+
" hack(host: string): Promise<number>;",
|
|
693
|
+
"}",
|
|
694
|
+
"declare var ns: NS;",
|
|
695
|
+
].join("\n"));
|
|
696
|
+
var engine = make_engine({}, [], reg);
|
|
697
|
+
// ns.hacknet. → should show Hacknet members
|
|
698
|
+
var list = engine.getCompletions(null, pos(1, 12), "ns.hacknet.", MockKind);
|
|
699
|
+
assert_has(list, 'purchaseNode', 'ns.hacknet.purchaseNode from DTS chain');
|
|
700
|
+
assert_has(list, 'getNodeStats', 'ns.hacknet.getNodeStats from DTS chain');
|
|
701
|
+
assert_missing(list, 'hack', 'NS.hack not in Hacknet');
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
|
|
705
|
+
{
|
|
706
|
+
name: "dot_dts_multi_level_prefix_filter",
|
|
707
|
+
description: "Multi-level dot completion respects prefix filter",
|
|
708
|
+
run: function () {
|
|
709
|
+
var reg = new DtsRegistry();
|
|
710
|
+
reg.addDts("lib", [
|
|
711
|
+
"interface Hacknet {",
|
|
712
|
+
" purchaseNode(): number;",
|
|
713
|
+
" purchaseUpgrade(index: number): boolean;",
|
|
714
|
+
" getNodeStats(index: number): object;",
|
|
715
|
+
"}",
|
|
716
|
+
"interface NS {",
|
|
717
|
+
" readonly hacknet: Hacknet;",
|
|
718
|
+
"}",
|
|
719
|
+
"declare var ns: NS;",
|
|
720
|
+
].join("\n"));
|
|
721
|
+
var engine = make_engine({}, [], reg);
|
|
722
|
+
var list = engine.getCompletions(null, pos(1, 15), "ns.hacknet.pur", MockKind);
|
|
723
|
+
assert_has(list, 'purchaseNode', 'purchaseNode matches pur');
|
|
724
|
+
assert_has(list, 'purchaseUpgrade', 'purchaseUpgrade matches pur');
|
|
725
|
+
assert_missing(list,'getNodeStats', 'getNodeStats does not match pur');
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
|
|
729
|
+
{
|
|
730
|
+
name: "dot_dts_multi_level_unknown_path",
|
|
731
|
+
description: "Multi-level dot on unknown path returns empty list",
|
|
732
|
+
run: function () {
|
|
733
|
+
var reg = new DtsRegistry();
|
|
734
|
+
reg.addDts("lib", "declare var ns: NS;");
|
|
735
|
+
var engine = make_engine({}, [], reg);
|
|
736
|
+
var list = engine.getCompletions(null, pos(1, 14), "ns.unknown.", MockKind);
|
|
737
|
+
assert.strictEqual(list.suggestions.length, 0, 'unknown member path → empty');
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
|
|
741
|
+
{
|
|
742
|
+
name: "dot_dts_return_type_simple",
|
|
743
|
+
description: "x = ns.getServer(...) gives x. the members of the return type (Server)",
|
|
744
|
+
run: function () {
|
|
745
|
+
var reg = new DtsRegistry();
|
|
746
|
+
reg.addDts("lib", [
|
|
747
|
+
"interface Server {",
|
|
748
|
+
" hostname: string;",
|
|
749
|
+
" hasAdminRights: boolean;",
|
|
750
|
+
" hackDifficulty: number;",
|
|
751
|
+
"}",
|
|
752
|
+
"interface NS {",
|
|
753
|
+
" getServer(host: string): Server;",
|
|
754
|
+
" hack(host: string): Promise<number>;",
|
|
755
|
+
"}",
|
|
756
|
+
"declare var ns: NS;",
|
|
757
|
+
].join("\n"));
|
|
758
|
+
var engine = make_engine({}, [], reg);
|
|
759
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
760
|
+
var scopeMap = analyzer.analyze([
|
|
761
|
+
"server = ns.getServer('n00dles')",
|
|
762
|
+
"pass",
|
|
763
|
+
].join("\n"), {});
|
|
764
|
+
|
|
765
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "server.", MockKind);
|
|
766
|
+
assert_has(list, 'hostname', 'Server.hostname');
|
|
767
|
+
assert_has(list, 'hasAdminRights', 'Server.hasAdminRights');
|
|
768
|
+
assert_has(list, 'hackDifficulty', 'Server.hackDifficulty');
|
|
769
|
+
assert_missing(list, 'hack', 'NS.hack not in Server');
|
|
770
|
+
},
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
// ── DTS return-type: function-local scope fallback ────────────────
|
|
774
|
+
|
|
775
|
+
{
|
|
776
|
+
name: "dot_dts_return_type_in_function",
|
|
777
|
+
description: "server = ns.getServer(...) inside async def main gives server. Server members (scope fallback)",
|
|
778
|
+
run: function () {
|
|
779
|
+
var reg = new DtsRegistry();
|
|
780
|
+
reg.addDts("lib", [
|
|
781
|
+
"interface Server {",
|
|
782
|
+
" hostname: string;",
|
|
783
|
+
" hasAdminRights: boolean;",
|
|
784
|
+
"}",
|
|
785
|
+
"interface NS {",
|
|
786
|
+
" getServer(host?: string): Server;",
|
|
787
|
+
"}",
|
|
788
|
+
"declare var ns: NS;",
|
|
789
|
+
].join("\n"));
|
|
790
|
+
var engine = make_engine({}, [], reg);
|
|
791
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
792
|
+
// The parsed code ends at line 2; the user is typing server. on line 3
|
|
793
|
+
var scopeMap = analyzer.analyze([
|
|
794
|
+
"async def main(ns):",
|
|
795
|
+
" server = ns.getServer('n00dles')",
|
|
796
|
+
].join("\n"), {});
|
|
797
|
+
|
|
798
|
+
// Line 3 is past the end of the parsed scope — requires scope fallback
|
|
799
|
+
var list = engine.getCompletions(scopeMap, pos(3, 12), " server.", MockKind);
|
|
800
|
+
assert_has(list, 'hostname', 'Server.hostname via scope fallback');
|
|
801
|
+
assert_has(list, 'hasAdminRights', 'Server.hasAdminRights via scope fallback');
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
|
|
805
|
+
{
|
|
806
|
+
name: "dot_dts_return_type_union",
|
|
807
|
+
description: "getServer returning a union type (A | B) still yields members of the first type",
|
|
808
|
+
run: function () {
|
|
809
|
+
var reg = new DtsRegistry();
|
|
810
|
+
reg.addDts("lib", [
|
|
811
|
+
"interface Server {",
|
|
812
|
+
" hostname: string;",
|
|
813
|
+
" hasAdminRights: boolean;",
|
|
814
|
+
"}",
|
|
815
|
+
"interface DarkServer { darkProp: string; }",
|
|
816
|
+
"interface NS {",
|
|
817
|
+
" getServer(host?: string): Server | (DarkServer & { isOnline: boolean });",
|
|
818
|
+
"}",
|
|
819
|
+
"declare var ns: NS;",
|
|
820
|
+
].join("\n"));
|
|
821
|
+
var engine = make_engine({}, [], reg);
|
|
822
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
823
|
+
var scopeMap = analyzer.analyze([
|
|
824
|
+
"async def main(ns):",
|
|
825
|
+
" server = ns.getServer('n00dles')",
|
|
826
|
+
].join("\n"), {});
|
|
827
|
+
|
|
828
|
+
var list = engine.getCompletions(scopeMap, pos(3, 12), " server.", MockKind);
|
|
829
|
+
assert_has(list, 'hostname', 'Server.hostname (first type in union)');
|
|
830
|
+
assert_has(list, 'hasAdminRights', 'Server.hasAdminRights (first type in union)');
|
|
831
|
+
},
|
|
832
|
+
},
|
|
833
|
+
|
|
834
|
+
{
|
|
835
|
+
name: "dot_list_literal_in_function_scope_fallback",
|
|
836
|
+
description: "a = [1,2,3] inside async def main gives a. list members (scope fallback)",
|
|
837
|
+
run: function () {
|
|
838
|
+
var engine = make_engine({}, [], null,
|
|
839
|
+
require(path.join(__dirname, '../../src/monaco-language-service/builtins.js')));
|
|
840
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
841
|
+
var scopeMap = analyzer.analyze([
|
|
842
|
+
"async def main(ns):",
|
|
843
|
+
" a = [1,2,3]",
|
|
844
|
+
].join("\n"), {});
|
|
845
|
+
|
|
846
|
+
// Line 3 is past the end of the parsed scope
|
|
847
|
+
var list = engine.getCompletions(scopeMap, pos(3, 7), " a.", MockKind);
|
|
848
|
+
assert_has(list, 'length', 'list.length via scope fallback');
|
|
849
|
+
assert_has(list, 'append', 'list.append via scope fallback');
|
|
850
|
+
assert_has(list, 'push', 'list.push via scope fallback');
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
{
|
|
855
|
+
name: "dot_dts_return_type_nested",
|
|
856
|
+
description: "x = ns.hacknet.getNodeStats(...) gives x. the members of the return type",
|
|
857
|
+
run: function () {
|
|
858
|
+
var reg = new DtsRegistry();
|
|
859
|
+
reg.addDts("lib", [
|
|
860
|
+
"interface NodeStats {",
|
|
861
|
+
" name: string;",
|
|
862
|
+
" level: number;",
|
|
863
|
+
" ram: number;",
|
|
864
|
+
"}",
|
|
865
|
+
"interface Hacknet {",
|
|
866
|
+
" getNodeStats(index: number): NodeStats;",
|
|
867
|
+
" purchaseNode(): number;",
|
|
868
|
+
"}",
|
|
869
|
+
"interface NS {",
|
|
870
|
+
" readonly hacknet: Hacknet;",
|
|
871
|
+
"}",
|
|
872
|
+
"declare var ns: NS;",
|
|
873
|
+
].join("\n"));
|
|
874
|
+
var engine = make_engine({}, [], reg);
|
|
875
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
876
|
+
var scopeMap = analyzer.analyze([
|
|
877
|
+
"stats = ns.hacknet.getNodeStats(0)",
|
|
878
|
+
"pass",
|
|
879
|
+
].join("\n"), {});
|
|
880
|
+
|
|
881
|
+
var list = engine.getCompletions(scopeMap, pos(2, 1), "stats.", MockKind);
|
|
882
|
+
assert_has(list, 'name', 'NodeStats.name');
|
|
883
|
+
assert_has(list, 'level', 'NodeStats.level');
|
|
884
|
+
assert_has(list, 'ram', 'NodeStats.ram');
|
|
885
|
+
assert_missing(list, 'purchaseNode', 'Hacknet.purchaseNode not in NodeStats');
|
|
886
|
+
},
|
|
887
|
+
},
|
|
888
|
+
|
|
889
|
+
{
|
|
890
|
+
name: "dot_scopemap_wins_over_dts",
|
|
891
|
+
description: "ScopeMap class members take precedence over DTS for same name",
|
|
892
|
+
run: function () {
|
|
893
|
+
var reg = new DtsRegistry();
|
|
894
|
+
reg.addDts("lib", [
|
|
895
|
+
"declare namespace MyClass {",
|
|
896
|
+
" function dts_only(): void;",
|
|
897
|
+
"}",
|
|
898
|
+
].join("\n"));
|
|
899
|
+
var engine = make_engine({}, [], reg);
|
|
900
|
+
var analyzer = new SourceAnalyzer(RS);
|
|
901
|
+
var scopeMap = analyzer.analyze([
|
|
902
|
+
"class MyClass:",
|
|
903
|
+
" def user_method(self):",
|
|
904
|
+
" return 1",
|
|
905
|
+
"x = MyClass",
|
|
906
|
+
"pass",
|
|
907
|
+
].join("\n"), {});
|
|
908
|
+
// Query at line 5 (pass) — MyClass is in module scope
|
|
909
|
+
var list = engine.getCompletions(scopeMap, pos(5, 1), "MyClass.", MockKind);
|
|
910
|
+
assert_has(list, 'user_method', 'ScopeMap method present');
|
|
911
|
+
assert_missing(list,'dts_only', 'DTS member absent when ScopeMap matches');
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
|
|
915
|
+
// ── functools stdlib completions ──────────────────────────────────
|
|
916
|
+
|
|
917
|
+
{
|
|
918
|
+
name: "functools_from_import_completions",
|
|
919
|
+
description: "from functools import shows all functools exports via stdlibFiles",
|
|
920
|
+
run: function () {
|
|
921
|
+
var engine = make_engine({}, ['print', 'len', 'range'], null, null, { functools: FUNCTOOLS_SRC });
|
|
922
|
+
var list = engine.getCompletions(null, pos(1, 24), "from functools import ", MockKind);
|
|
923
|
+
assert_has(list, 'reduce', 'reduce in functools completions');
|
|
924
|
+
assert_has(list, 'partial', 'partial in functools completions');
|
|
925
|
+
assert_has(list, 'wraps', 'wraps in functools completions');
|
|
926
|
+
assert_has(list, 'lru_cache', 'lru_cache in functools completions');
|
|
927
|
+
assert_has(list, 'cache', 'cache in functools completions');
|
|
928
|
+
assert_has(list, 'total_ordering', 'total_ordering in functools completions');
|
|
929
|
+
assert_has(list, 'cmp_to_key', 'cmp_to_key in functools completions');
|
|
930
|
+
},
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
{
|
|
934
|
+
name: "functools_from_import_prefix",
|
|
935
|
+
description: "from functools import with prefix filters to matching names",
|
|
936
|
+
run: function () {
|
|
937
|
+
var engine = make_engine({}, ['print', 'len', 'range'], null, null, { functools: FUNCTOOLS_SRC });
|
|
938
|
+
var list = engine.getCompletions(null, pos(1, 27), "from functools import lru", MockKind);
|
|
939
|
+
assert_has(list, 'lru_cache', 'lru_cache matches prefix "lru"');
|
|
940
|
+
assert_missing(list,'reduce', 'reduce does not match prefix "lru"');
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
|
|
944
|
+
{
|
|
945
|
+
name: "functools_module_in_import",
|
|
946
|
+
description: "import shows functools when it is in stdlibFiles",
|
|
947
|
+
run: function () {
|
|
948
|
+
var engine = make_engine({}, ['print', 'len', 'range'], null, null, { functools: FUNCTOOLS_SRC });
|
|
949
|
+
var list = engine.getCompletions(null, pos(1, 7), "import ", MockKind);
|
|
950
|
+
assert_has(list, 'functools', 'functools module in import suggestions');
|
|
951
|
+
},
|
|
952
|
+
},
|
|
953
|
+
|
|
954
|
+
// ── collections stdlib completions ────────────────────────────────
|
|
955
|
+
|
|
956
|
+
{
|
|
957
|
+
name: "collections_from_import_completions",
|
|
958
|
+
description: "from collections import shows all collections exports",
|
|
959
|
+
run: function () {
|
|
960
|
+
var engine = make_engine({}, ['print', 'len', 'range'], null, null, { collections: COLLECTIONS_SRC });
|
|
961
|
+
var list = engine.getCompletions(null, pos(1, 28), "from collections import ", MockKind);
|
|
962
|
+
assert_has(list, 'namedtuple', 'namedtuple in collections completions');
|
|
963
|
+
assert_has(list, 'deque', 'deque in collections completions');
|
|
964
|
+
assert_has(list, 'Counter', 'Counter in collections completions');
|
|
965
|
+
assert_has(list, 'OrderedDict', 'OrderedDict in collections completions');
|
|
966
|
+
assert_has(list, 'defaultdict', 'defaultdict in collections completions');
|
|
967
|
+
},
|
|
968
|
+
},
|
|
969
|
+
|
|
970
|
+
{
|
|
971
|
+
name: "collections_from_import_prefix",
|
|
972
|
+
description: "from collections import with prefix filters correctly",
|
|
973
|
+
run: function () {
|
|
974
|
+
var engine = make_engine({}, ['print', 'len', 'range'], null, null, { collections: COLLECTIONS_SRC });
|
|
975
|
+
var list = engine.getCompletions(null, pos(1, 32), "from collections import named", MockKind);
|
|
976
|
+
assert_has(list, 'namedtuple', 'namedtuple matches prefix "named"');
|
|
977
|
+
assert_missing(list,'deque', 'deque does not match prefix "named"');
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
|
|
981
|
+
{
|
|
982
|
+
name: "collections_module_in_import",
|
|
983
|
+
description: "import shows collections when it is in stdlibFiles",
|
|
984
|
+
run: function () {
|
|
985
|
+
var engine = make_engine({}, ['print', 'len', 'range'], null, null, { collections: COLLECTIONS_SRC });
|
|
986
|
+
var list = engine.getCompletions(null, pos(1, 7), "import ", MockKind);
|
|
987
|
+
assert_has(list, 'collections', 'collections module in import suggestions');
|
|
988
|
+
},
|
|
989
|
+
},
|
|
990
|
+
|
|
991
|
+
];
|
|
992
|
+
|
|
993
|
+
return TESTS;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// ---------------------------------------------------------------------------
|
|
997
|
+
// Runner
|
|
998
|
+
// ---------------------------------------------------------------------------
|
|
999
|
+
|
|
1000
|
+
function run_tests(TESTS, filter) {
|
|
1001
|
+
var tests = filter
|
|
1002
|
+
? TESTS.filter(function (t) { return t.name === filter; })
|
|
1003
|
+
: TESTS;
|
|
1004
|
+
|
|
1005
|
+
if (tests.length === 0) {
|
|
1006
|
+
console.error(colored("No test found: " + filter, "red"));
|
|
1007
|
+
process.exit(1);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
var failures = [];
|
|
1011
|
+
|
|
1012
|
+
tests.forEach(function (test) {
|
|
1013
|
+
try {
|
|
1014
|
+
test.run();
|
|
1015
|
+
console.log(colored("PASS " + test.name, "green") +
|
|
1016
|
+
" – " + test.description);
|
|
1017
|
+
} catch (e) {
|
|
1018
|
+
failures.push(test.name);
|
|
1019
|
+
var msg = e.message || String(e);
|
|
1020
|
+
console.log(colored("FAIL " + test.name, "red") +
|
|
1021
|
+
"\n " + msg + "\n");
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
console.log("");
|
|
1026
|
+
if (failures.length) {
|
|
1027
|
+
console.log(colored(failures.length + " test(s) failed.", "red"));
|
|
1028
|
+
} else {
|
|
1029
|
+
console.log(colored("All " + tests.length + " language-service-completions tests passed!", "green"));
|
|
1030
|
+
}
|
|
1031
|
+
process.exit(failures.length ? 1 : 0);
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
// ---------------------------------------------------------------------------
|
|
1035
|
+
// Entry point
|
|
1036
|
+
// ---------------------------------------------------------------------------
|
|
1037
|
+
|
|
1038
|
+
var completions_path = url.pathToFileURL(
|
|
1039
|
+
path.join(__dirname, "../../src/monaco-language-service/completions.js")
|
|
1040
|
+
).href;
|
|
1041
|
+
|
|
1042
|
+
var analyzer_path = url.pathToFileURL(
|
|
1043
|
+
path.join(__dirname, "../../src/monaco-language-service/analyzer.js")
|
|
1044
|
+
).href;
|
|
1045
|
+
|
|
1046
|
+
var dts_path = url.pathToFileURL(
|
|
1047
|
+
path.join(__dirname, "../../src/monaco-language-service/dts.js")
|
|
1048
|
+
).href;
|
|
1049
|
+
|
|
1050
|
+
var filter = process.argv[2] || null;
|
|
1051
|
+
|
|
1052
|
+
Promise.all([
|
|
1053
|
+
import(completions_path),
|
|
1054
|
+
import(analyzer_path),
|
|
1055
|
+
import(dts_path),
|
|
1056
|
+
]).then(function (mods) {
|
|
1057
|
+
var CompletionEngine = mods[0].CompletionEngine;
|
|
1058
|
+
var detect_context = mods[0].detect_context;
|
|
1059
|
+
var SourceAnalyzer = mods[1].SourceAnalyzer;
|
|
1060
|
+
var DtsRegistry = mods[2].DtsRegistry;
|
|
1061
|
+
var RS = compiler_module.create_compiler();
|
|
1062
|
+
var TESTS = make_tests(CompletionEngine, detect_context, SourceAnalyzer, DtsRegistry, RS);
|
|
1063
|
+
run_tests(TESTS, filter);
|
|
1064
|
+
}).catch(function (e) {
|
|
1065
|
+
console.error(colored("Failed to load completions module:", "red"), e);
|
|
1066
|
+
process.exit(1);
|
|
1067
|
+
});
|