rapydscript-ns 0.8.1 → 0.8.3
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 +31 -0
- package/CONTRIBUTORS +3 -2
- package/PYTHON_DIFFERENCES_REPORT.md +291 -0
- package/PYTHON_FEATURE_COVERAGE.md +200 -0
- package/README.md +480 -79
- package/TODO.md +6 -318
- package/hack_demo.pyj +112 -0
- package/language-service/index.js +4474 -0
- package/language-service/language-service.d.ts +40 -0
- package/package.json +9 -10
- package/src/ast.pyj +30 -6
- package/src/baselib-builtins.pyj +181 -11
- package/src/baselib-containers.pyj +154 -5
- package/src/baselib-errors.pyj +3 -0
- package/src/baselib-internal.pyj +40 -1
- package/src/baselib-str.pyj +42 -1
- package/src/lib/collections.pyj +1 -1
- package/src/lib/numpy.pyj +10 -10
- package/src/monaco-language-service/analyzer.js +132 -22
- package/src/monaco-language-service/builtins.js +22 -2
- package/src/monaco-language-service/completions.js +224 -3
- package/src/monaco-language-service/diagnostics.js +55 -5
- package/src/monaco-language-service/index.js +26 -5
- package/src/monaco-language-service/scope.js +3 -0
- package/src/output/classes.pyj +20 -3
- package/src/output/codegen.pyj +38 -3
- package/src/output/functions.pyj +35 -25
- package/src/output/loops.pyj +64 -11
- package/src/output/modules.pyj +1 -4
- package/src/output/operators.pyj +67 -1
- package/src/output/statements.pyj +7 -3
- package/src/output/stream.pyj +6 -13
- package/src/parse.pyj +94 -14
- package/src/tokenizer.pyj +1 -0
- package/test/baselib.pyj +4 -4
- package/test/classes.pyj +56 -17
- package/test/collections.pyj +5 -5
- package/test/python_compat.pyj +326 -0
- package/test/python_features.pyj +1271 -0
- package/test/slice.pyj +105 -0
- package/test/str.pyj +25 -0
- package/test/unit/fixtures/fibonacci_expected.js +1 -1
- package/test/unit/index.js +119 -7
- package/test/unit/language-service-builtins.js +70 -0
- package/test/unit/language-service-bundle.js +83 -0
- package/test/unit/language-service-completions.js +289 -0
- package/test/unit/language-service-index.js +350 -0
- package/test/unit/language-service-scope.js +255 -0
- package/test/unit/language-service.js +158 -1
- package/test/unit/run-language-service.js +2 -0
- package/test/unit/web-repl.js +134 -0
- package/tools/build-language-service.js +2 -2
- package/tools/compiler.js +0 -24
- package/tools/export.js +3 -37
- package/tools/lint.js +1 -1
- package/tools/self.js +1 -9
- package/web-repl/rapydscript.js +6 -40
- package/web-repl/language-service.js +0 -4084
|
@@ -8,6 +8,25 @@
|
|
|
8
8
|
import { SourceAnalyzer } from './analyzer.js';
|
|
9
9
|
import { resolve_first_type } from './dts.js';
|
|
10
10
|
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Language keywords
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* RapydScript / Python keywords that are valid identifier-completion tokens.
|
|
17
|
+
* Shown in the `identifier` completion context only (not dot / import contexts).
|
|
18
|
+
*/
|
|
19
|
+
export const KEYWORDS = [
|
|
20
|
+
'and', 'as', 'assert', 'async', 'await',
|
|
21
|
+
'break', 'class', 'continue', 'def', 'del',
|
|
22
|
+
'elif', 'else', 'except', 'finally', 'for',
|
|
23
|
+
'from', 'global', 'if', 'import', 'in', 'is',
|
|
24
|
+
'lambda', 'nonlocal', 'not', 'or', 'pass',
|
|
25
|
+
'raise', 'return', 'try', 'while', 'with',
|
|
26
|
+
'yield',
|
|
27
|
+
'True', 'False', 'None',
|
|
28
|
+
];
|
|
29
|
+
|
|
11
30
|
// ---------------------------------------------------------------------------
|
|
12
31
|
// Context detection
|
|
13
32
|
// ---------------------------------------------------------------------------
|
|
@@ -26,6 +45,13 @@ import { resolve_first_type } from './dts.js';
|
|
|
26
45
|
* @returns {{ type: string, objectName?: string, moduleName?: string, prefix: string }}
|
|
27
46
|
*/
|
|
28
47
|
export function detect_context(linePrefix) {
|
|
48
|
+
// `obj.method().attr` — dot access on a call result (single-level call expression).
|
|
49
|
+
// Must be checked before the plain dot match since `[\w.]+` cannot span `()`.
|
|
50
|
+
const call_result_match = linePrefix.match(/([\w.]+)\([^)]*\)\.([\w]*)$/);
|
|
51
|
+
if (call_result_match) {
|
|
52
|
+
return { type: 'call_result', callPath: call_result_match[1], prefix: call_result_match[2] };
|
|
53
|
+
}
|
|
54
|
+
|
|
29
55
|
// `obj.attr` or `obj.sub.attr` — dot access (captures multi-level path)
|
|
30
56
|
const dot_match = linePrefix.match(/([\w.]+)\.([\w]*)$/);
|
|
31
57
|
if (dot_match) {
|
|
@@ -246,6 +272,9 @@ export class CompletionEngine {
|
|
|
246
272
|
getCompletions(scopeMap, position, linePrefix, monacoKind) {
|
|
247
273
|
const ctx = detect_context(linePrefix);
|
|
248
274
|
|
|
275
|
+
if (ctx.type === 'call_result') {
|
|
276
|
+
return { suggestions: this._call_result_completions(position, ctx, monacoKind, scopeMap) };
|
|
277
|
+
}
|
|
249
278
|
if (ctx.type === 'dot') {
|
|
250
279
|
return { suggestions: this._dot_completions(scopeMap, position, ctx, monacoKind) };
|
|
251
280
|
}
|
|
@@ -267,7 +296,25 @@ export class CompletionEngine {
|
|
|
267
296
|
const seen = new Set();
|
|
268
297
|
|
|
269
298
|
if (scopeMap) {
|
|
270
|
-
|
|
299
|
+
let symbols = scopeMap.getSymbolsAtPosition(position.lineNumber, position.column);
|
|
300
|
+
|
|
301
|
+
// Fallback: cursor is on a new line not yet covered by the last debounced parse
|
|
302
|
+
// (e.g. the user typed Return and is now on a line past the end of the last AST).
|
|
303
|
+
// Collect all symbols from all frames, innermost-first, so locals remain visible.
|
|
304
|
+
if (symbols.length === 0 && scopeMap.frames.length > 0) {
|
|
305
|
+
const all_frames = scopeMap.frames.slice().sort((a, b) => b.depth - a.depth);
|
|
306
|
+
const fallback_seen = new Set();
|
|
307
|
+
symbols = [];
|
|
308
|
+
for (const frame of all_frames) {
|
|
309
|
+
for (const [name, sym] of frame.symbols) {
|
|
310
|
+
if (!fallback_seen.has(name)) {
|
|
311
|
+
fallback_seen.add(name);
|
|
312
|
+
symbols.push(sym);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
271
318
|
for (const sym of symbols) {
|
|
272
319
|
if (!ctx.prefix || sym.name.startsWith(ctx.prefix)) {
|
|
273
320
|
if (!seen.has(sym.name)) {
|
|
@@ -279,7 +326,7 @@ export class CompletionEngine {
|
|
|
279
326
|
}
|
|
280
327
|
}
|
|
281
328
|
|
|
282
|
-
// Builtins (
|
|
329
|
+
// Builtins (lower priority) — use rich stub item when available
|
|
283
330
|
for (const name of this._builtinNames) {
|
|
284
331
|
if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
|
|
285
332
|
seen.add(name);
|
|
@@ -291,9 +338,100 @@ export class CompletionEngine {
|
|
|
291
338
|
}
|
|
292
339
|
}
|
|
293
340
|
|
|
341
|
+
// Keywords (lowest priority)
|
|
342
|
+
const kw_kind = monacoKind.Keyword !== undefined ? monacoKind.Keyword : monacoKind.Variable;
|
|
343
|
+
for (const kw of KEYWORDS) {
|
|
344
|
+
if (!seen.has(kw) && (!ctx.prefix || kw.startsWith(ctx.prefix))) {
|
|
345
|
+
seen.add(kw);
|
|
346
|
+
items.push({
|
|
347
|
+
label: kw,
|
|
348
|
+
kind: kw_kind,
|
|
349
|
+
sortText: '3_' + kw,
|
|
350
|
+
insertText: kw,
|
|
351
|
+
range,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
294
356
|
return items;
|
|
295
357
|
}
|
|
296
358
|
|
|
359
|
+
// ---- Return-type helpers -----------------------------------------------
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Analyze a module and return the `return_type` of a named function.
|
|
363
|
+
* @param {string} func_name
|
|
364
|
+
* @param {string} module_name
|
|
365
|
+
* @returns {string|null}
|
|
366
|
+
*/
|
|
367
|
+
_get_return_type_from_module(func_name, module_name) {
|
|
368
|
+
const src = this._virtualFiles[module_name] || this._stdlibFiles[module_name];
|
|
369
|
+
if (!src) return null;
|
|
370
|
+
let mod_map;
|
|
371
|
+
try {
|
|
372
|
+
mod_map = this._analyzer.analyze(src, {});
|
|
373
|
+
} catch (_e) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
const mod_frame = mod_map.frames.find(f => f.kind === 'module');
|
|
377
|
+
if (!mod_frame) return null;
|
|
378
|
+
const fn_sym = mod_frame.getSymbol(func_name);
|
|
379
|
+
if (fn_sym && fn_sym.return_type) return fn_sym.return_type;
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Resolve a type name (e.g. from inferred_class) through function return annotations,
|
|
385
|
+
* including cross-file imports. Returns the resolved type name, or null if unresolvable.
|
|
386
|
+
* @param {string} name
|
|
387
|
+
* @param {import('./scope.js').ScopeMap} scopeMap
|
|
388
|
+
* @returns {string|null}
|
|
389
|
+
*/
|
|
390
|
+
_resolve_type_through_functions(name, scopeMap) {
|
|
391
|
+
let sym = null;
|
|
392
|
+
for (const frame of scopeMap.frames) {
|
|
393
|
+
const s = frame.getSymbol(name);
|
|
394
|
+
if (s) { sym = s; break; }
|
|
395
|
+
}
|
|
396
|
+
if (!sym) return null;
|
|
397
|
+
|
|
398
|
+
if (sym.kind === 'function' || sym.kind === 'method') {
|
|
399
|
+
// return_type is set by explicit annotation or inferred by the analyzer
|
|
400
|
+
if (sym.return_type) return sym.return_type;
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (sym.kind === 'import' && sym.source_module) {
|
|
405
|
+
const orig = sym.original_name || sym.name;
|
|
406
|
+
return this._get_return_type_from_module(orig, sym.source_module);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get the member map for a given type name.
|
|
414
|
+
* Checks builtins, then user class frames. Returns null if unknown.
|
|
415
|
+
* @param {string} type_name
|
|
416
|
+
* @param {import('./scope.js').ScopeMap|null} scopeMap
|
|
417
|
+
* @returns {Map|null}
|
|
418
|
+
*/
|
|
419
|
+
_get_members_for_type(type_name, scopeMap) {
|
|
420
|
+
if (!type_name) return null;
|
|
421
|
+
if (this._builtins) {
|
|
422
|
+
const m = this._builtins.getTypeMembers(type_name);
|
|
423
|
+
if (m) return m;
|
|
424
|
+
}
|
|
425
|
+
if (scopeMap) {
|
|
426
|
+
for (const frame of scopeMap.frames) {
|
|
427
|
+
if (frame.kind === 'class' && frame.name === type_name) {
|
|
428
|
+
return frame.symbols;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
|
|
297
435
|
// ---- Dot completions ---------------------------------------------------
|
|
298
436
|
|
|
299
437
|
_dot_completions(scopeMap, position, ctx, monacoKind) {
|
|
@@ -361,6 +499,13 @@ export class CompletionEngine {
|
|
|
361
499
|
}
|
|
362
500
|
}
|
|
363
501
|
|
|
502
|
+
// Resolve class_name through function return type annotations.
|
|
503
|
+
// e.g. x = getAllServers() → inferred_class='getAllServers' → resolve 'list'
|
|
504
|
+
if (class_name) {
|
|
505
|
+
const resolved = this._resolve_type_through_functions(class_name, scopeMap);
|
|
506
|
+
if (resolved) class_name = resolved;
|
|
507
|
+
}
|
|
508
|
+
|
|
364
509
|
if (class_name) {
|
|
365
510
|
for (const frame of scopeMap.frames) {
|
|
366
511
|
if (frame.kind === 'class' && frame.name === class_name) {
|
|
@@ -381,7 +526,13 @@ export class CompletionEngine {
|
|
|
381
526
|
// 1.5. Built-in type members — list, str, dict, number.
|
|
382
527
|
// Used when inferred_class names a built-in type, not a user class.
|
|
383
528
|
if (!scope_matched && this._builtins && obj_sym && obj_sym.inferred_class) {
|
|
384
|
-
|
|
529
|
+
// Re-resolve in case inferred_class itself is a function name.
|
|
530
|
+
let type_name = obj_sym.inferred_class;
|
|
531
|
+
if (scopeMap) {
|
|
532
|
+
const resolved = this._resolve_type_through_functions(type_name, scopeMap);
|
|
533
|
+
if (resolved) type_name = resolved;
|
|
534
|
+
}
|
|
535
|
+
const members = this._builtins.getTypeMembers(type_name);
|
|
385
536
|
if (members) {
|
|
386
537
|
scope_matched = true;
|
|
387
538
|
for (const [name, member] of members) {
|
|
@@ -444,6 +595,76 @@ export class CompletionEngine {
|
|
|
444
595
|
return items;
|
|
445
596
|
}
|
|
446
597
|
|
|
598
|
+
// ---- Call-result dot completions (e.g. ns.self().attr) -----------------
|
|
599
|
+
|
|
600
|
+
_call_result_completions(position, ctx, monacoKind, scopeMap) {
|
|
601
|
+
const range = word_range(position, ctx.prefix);
|
|
602
|
+
const items = [];
|
|
603
|
+
const seen = new Set();
|
|
604
|
+
const call_path = ctx.callPath;
|
|
605
|
+
const last_dot = call_path.lastIndexOf('.');
|
|
606
|
+
|
|
607
|
+
// --- DTS path (existing logic) ---
|
|
608
|
+
if (this._dts) {
|
|
609
|
+
let member_ti = null;
|
|
610
|
+
if (last_dot > 0) {
|
|
611
|
+
const object_path = call_path.slice(0, last_dot);
|
|
612
|
+
const method_name = call_path.slice(last_dot + 1);
|
|
613
|
+
member_ti = this._dts.getMemberInfo(object_path, method_name);
|
|
614
|
+
} else {
|
|
615
|
+
member_ti = this._dts.getGlobal(call_path);
|
|
616
|
+
}
|
|
617
|
+
if (member_ti && member_ti.return_type) {
|
|
618
|
+
const return_ti = this._dts.getGlobal(resolve_first_type(member_ti.return_type));
|
|
619
|
+
if (return_ti && return_ti.members) {
|
|
620
|
+
for (const [name, member] of return_ti.members) {
|
|
621
|
+
if (!ctx.prefix || name.startsWith(ctx.prefix)) {
|
|
622
|
+
seen.add(name);
|
|
623
|
+
items.push(_dts_member_to_item(member, range, monacoKind));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// --- User-defined function return types (scopeMap) ---
|
|
631
|
+
if (scopeMap && last_dot < 0) {
|
|
632
|
+
// Simple call like getAllServers() — look up function in scope
|
|
633
|
+
let sym = null;
|
|
634
|
+
for (const frame of scopeMap.frames) {
|
|
635
|
+
const s = frame.getSymbol(call_path);
|
|
636
|
+
if (s) { sym = s; break; }
|
|
637
|
+
}
|
|
638
|
+
if (sym) {
|
|
639
|
+
let return_type = null;
|
|
640
|
+
if (sym.kind === 'function' || sym.kind === 'method') {
|
|
641
|
+
return_type = sym.return_type;
|
|
642
|
+
} else if (sym.kind === 'import' && sym.source_module) {
|
|
643
|
+
const orig = sym.original_name || sym.name;
|
|
644
|
+
return_type = this._get_return_type_from_module(orig, sym.source_module);
|
|
645
|
+
}
|
|
646
|
+
if (return_type) {
|
|
647
|
+
const members = this._get_members_for_type(return_type, scopeMap);
|
|
648
|
+
if (members) {
|
|
649
|
+
for (const [name, member] of members) {
|
|
650
|
+
if (!seen.has(name) && (!ctx.prefix || name.startsWith(ctx.prefix))) {
|
|
651
|
+
seen.add(name);
|
|
652
|
+
// members may be SymbolInfo (class frame) or TypeInfo/BuiltinInfo
|
|
653
|
+
const item = member.kind === 'function' || member.kind === 'method' ||
|
|
654
|
+
member.kind === 'variable' || member.kind === 'parameter'
|
|
655
|
+
? symbol_to_item(member, range, monacoKind, '0')
|
|
656
|
+
: _dts_member_to_item(member, range, monacoKind);
|
|
657
|
+
items.push(item);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return items;
|
|
666
|
+
}
|
|
667
|
+
|
|
447
668
|
// ---- from X import completions -----------------------------------------
|
|
448
669
|
|
|
449
670
|
_from_import_completions(position, ctx, monacoKind) {
|
|
@@ -16,6 +16,7 @@ const MESSAGES = {
|
|
|
16
16
|
'func-in-branch': 'Named functions/classes inside a branch are not allowed in strict mode',
|
|
17
17
|
'syntax-err': 'Syntax error: {name}',
|
|
18
18
|
'import-err': 'Import error: {name}',
|
|
19
|
+
'bad-import': 'Unknown module: "{name}"',
|
|
19
20
|
'def-after-use': 'The symbol "{name}" is defined (at line {line}) after it is used',
|
|
20
21
|
'dup-key': 'Duplicate key "{name}" in object literal',
|
|
21
22
|
'dup-method': 'The method "{name}" was defined previously at line: {line}',
|
|
@@ -27,13 +28,13 @@ export const BASE_BUILTINS = (
|
|
|
27
28
|
' eval undefined arguments abs max min enumerate pow callable reversed sum' +
|
|
28
29
|
' getattr isFinite setattr hasattr parseInt parseFloat options_object' +
|
|
29
30
|
' isNaN JSON Math list set list_wrap ρσ_modules require bool int bin' +
|
|
30
|
-
' float iter Error EvalError set_wrap RangeError ReferenceError SyntaxError' +
|
|
31
|
+
' float iter Error EvalError set_wrap frozenset RangeError ReferenceError SyntaxError' +
|
|
31
32
|
' str TypeError URIError Exception AssertionError IndexError AttributeError KeyError' +
|
|
32
33
|
' ValueError ZeroDivisionError map hex filter zip dict dict_wrap UnicodeDecodeError HTMLCollection' +
|
|
33
34
|
' NodeList alert console Node Symbol NamedNodeMap ρσ_eslice ρσ_delslice Number' +
|
|
34
35
|
' Boolean encodeURIComponent decodeURIComponent setTimeout setInterval' +
|
|
35
36
|
' setImmediate clearTimeout clearInterval clearImmediate requestAnimationFrame' +
|
|
36
|
-
' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis'
|
|
37
|
+
' id repr sorted __name__ equals get_module ρσ_str jstype divmod NaN super Ellipsis slice'
|
|
37
38
|
).split(' ');
|
|
38
39
|
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
@@ -44,7 +45,7 @@ function has_prop(obj, name) {
|
|
|
44
45
|
return Object.prototype.hasOwnProperty.call(obj, name);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function build_scoped_flags(flags_str) {
|
|
48
|
+
export function build_scoped_flags(flags_str) {
|
|
48
49
|
const result = Object.create(null);
|
|
49
50
|
if (!flags_str) return result;
|
|
50
51
|
flags_str.split(',').forEach(flag => {
|
|
@@ -205,7 +206,7 @@ Scope.prototype.messages = function() {
|
|
|
205
206
|
// Linter — mirrors the Linter in tools/lint.js
|
|
206
207
|
// ---------------------------------------------------------------------------
|
|
207
208
|
|
|
208
|
-
function Linter(RS, toplevel, code, builtins) {
|
|
209
|
+
function Linter(RS, toplevel, code, builtins, knownModules) {
|
|
209
210
|
this.RS = RS;
|
|
210
211
|
this.scopes = [];
|
|
211
212
|
this.walked_scopes = [];
|
|
@@ -213,6 +214,7 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
213
214
|
this.branches = [];
|
|
214
215
|
this.messages = [];
|
|
215
216
|
this.builtins = builtins;
|
|
217
|
+
this._knownModules = knownModules || null;
|
|
216
218
|
|
|
217
219
|
this.add_binding = function(name, binding_node) {
|
|
218
220
|
const scope = this.scopes[this.scopes.length - 1];
|
|
@@ -240,6 +242,26 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
240
242
|
const name = node.alias ? node.alias.name : node.key.split('.', 1)[0];
|
|
241
243
|
this.add_binding(name, node.alias || node);
|
|
242
244
|
}
|
|
245
|
+
// Flag imports of modules not present in the known-module registry.
|
|
246
|
+
// Only active when at least one virtualFile or stdlibFile is registered,
|
|
247
|
+
// to avoid false positives when no module system is configured.
|
|
248
|
+
if (this._knownModules) {
|
|
249
|
+
const root = node.key.split('.')[0];
|
|
250
|
+
if (!has_prop(this._knownModules, root)) {
|
|
251
|
+
const sline = node.start ? node.start.line : 1;
|
|
252
|
+
const scol = node.start ? node.start.col : 0;
|
|
253
|
+
this.messages.push({
|
|
254
|
+
start_line: sline,
|
|
255
|
+
start_col: scol,
|
|
256
|
+
end_line: sline,
|
|
257
|
+
end_col: scol + node.key.length - 1,
|
|
258
|
+
ident: 'bad-import',
|
|
259
|
+
message: MESSAGES['bad-import'].replace('{name}', node.key),
|
|
260
|
+
level: ERROR,
|
|
261
|
+
name: node.key,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
243
265
|
};
|
|
244
266
|
|
|
245
267
|
this.handle_imported_var = function() {
|
|
@@ -312,6 +334,23 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
312
334
|
}
|
|
313
335
|
};
|
|
314
336
|
|
|
337
|
+
this.handle_annotated_assign = function() {
|
|
338
|
+
const node = this.current_node;
|
|
339
|
+
// Variable type annotation: `x: int = value` or `x: int`
|
|
340
|
+
// Register the target as a binding (with or without a value).
|
|
341
|
+
if (node.target instanceof RS.AST_SymbolRef) {
|
|
342
|
+
node.target.lint_visited = true;
|
|
343
|
+
this.current_node = node.target;
|
|
344
|
+
this.add_binding(node.target.name);
|
|
345
|
+
this.current_node = node;
|
|
346
|
+
}
|
|
347
|
+
// Mark annotation symbol refs as visited so they aren't treated as uses
|
|
348
|
+
// of undefined variables (e.g. `int`, `str` are type names, not runtime refs).
|
|
349
|
+
if (node.annotation instanceof RS.AST_SymbolRef) {
|
|
350
|
+
node.annotation.lint_visited = true;
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
|
|
315
354
|
this.handle_vardef = function() {
|
|
316
355
|
const node = this.current_node;
|
|
317
356
|
if (node.name instanceof RS.AST_SymbolNonlocal) {
|
|
@@ -461,6 +500,7 @@ function Linter(RS, toplevel, code, builtins) {
|
|
|
461
500
|
else if (node instanceof RS.AST_Assign) this.handle_assign();
|
|
462
501
|
else if (node instanceof RS.AST_NamedExpr) this.handle_named_expr();
|
|
463
502
|
else if (node instanceof RS.AST_VarDef) this.handle_vardef();
|
|
503
|
+
else if (RS.AST_AnnotatedAssign && node instanceof RS.AST_AnnotatedAssign) this.handle_annotated_assign();
|
|
464
504
|
else if (node instanceof RS.AST_SymbolRef) this.handle_symbol_ref();
|
|
465
505
|
else if (node instanceof RS.AST_Decorator) this.handle_decorator();
|
|
466
506
|
else if (node instanceof RS.AST_SymbolFunarg) this.handle_symbol_funarg();
|
|
@@ -634,7 +674,17 @@ export class Diagnostics {
|
|
|
634
674
|
}
|
|
635
675
|
|
|
636
676
|
// --- Lint ---
|
|
637
|
-
|
|
677
|
+
// Build known-module set for import validation.
|
|
678
|
+
// Only activates when the caller has registered at least one module.
|
|
679
|
+
let knownModules = null;
|
|
680
|
+
const vf = options.virtualFiles;
|
|
681
|
+
const sf = options.stdlibFiles;
|
|
682
|
+
if ((vf && Object.keys(vf).length > 0) || (sf && Object.keys(sf).length > 0)) {
|
|
683
|
+
knownModules = Object.create(null);
|
|
684
|
+
if (vf) Object.keys(vf).forEach(k => { knownModules[k] = true; });
|
|
685
|
+
if (sf) Object.keys(sf).forEach(k => { knownModules[k] = true; });
|
|
686
|
+
}
|
|
687
|
+
const linter = new Linter(RS, toplevel, code, this._builtins, knownModules);
|
|
638
688
|
toplevel.walk(linter);
|
|
639
689
|
messages = linter.resolve(noqa);
|
|
640
690
|
|
|
@@ -36,6 +36,13 @@ class ModelState {
|
|
|
36
36
|
this._timer = null;
|
|
37
37
|
this._scopeMap = null; // most recent ScopeMap for this model
|
|
38
38
|
|
|
39
|
+
// Derive the module name from the model URI so this model's content can
|
|
40
|
+
// be registered in _virtualFiles for cross-file import resolution.
|
|
41
|
+
// e.g. URI path "/scripts/utils.py" → module name "utils"
|
|
42
|
+
const uri_path = (model.uri && (model.uri.path || model.uri.fsPath)) || '';
|
|
43
|
+
const basename = uri_path.split('/').pop() || '';
|
|
44
|
+
this._module_name = basename.replace(/\.pyj?x?$/, '') || null;
|
|
45
|
+
|
|
39
46
|
// Run immediately on first attach
|
|
40
47
|
this._schedule(0);
|
|
41
48
|
|
|
@@ -53,7 +60,13 @@ class ModelState {
|
|
|
53
60
|
_run() {
|
|
54
61
|
const service = this._service;
|
|
55
62
|
const code = this._model.getValue();
|
|
56
|
-
const opts = { virtualFiles: service._virtualFiles };
|
|
63
|
+
const opts = { virtualFiles: service._virtualFiles, stdlibFiles: service._stdlibFiles };
|
|
64
|
+
|
|
65
|
+
// Register this model's content in the shared virtualFiles pool so other
|
|
66
|
+
// open models can resolve cross-file imports from it.
|
|
67
|
+
if (this._module_name) {
|
|
68
|
+
service._virtualFiles[this._module_name] = code;
|
|
69
|
+
}
|
|
57
70
|
|
|
58
71
|
// Diagnostics (syntax errors + lint markers)
|
|
59
72
|
const markers = service._diagnostics.check(code, {
|
|
@@ -69,6 +82,10 @@ class ModelState {
|
|
|
69
82
|
dispose() {
|
|
70
83
|
clearTimeout(this._timer);
|
|
71
84
|
this._subscription.dispose();
|
|
85
|
+
// Remove this model's content from the virtualFiles pool on detach.
|
|
86
|
+
if (this._module_name) {
|
|
87
|
+
delete this._service._virtualFiles[this._module_name];
|
|
88
|
+
}
|
|
72
89
|
// Clear markers when detaching
|
|
73
90
|
this._service._monaco.editor.setModelMarkers(this._model, LANGUAGE_ID, []);
|
|
74
91
|
}
|
|
@@ -108,9 +125,12 @@ class RapydScriptLanguageService {
|
|
|
108
125
|
this._builtins = new BuiltinsRegistry();
|
|
109
126
|
this._builtins.enablePythonizeStrings();
|
|
110
127
|
|
|
111
|
-
// Merge BASE_BUILTINS +
|
|
128
|
+
// Merge BASE_BUILTINS + DTS globals for completions.
|
|
129
|
+
// NOTE: _extraBuiltinNames (NS API keys like grow, growthAnalyze, etc.) are
|
|
130
|
+
// intentionally excluded here — they are handled by the DTS registry as dot-
|
|
131
|
+
// completions on ns.* and should NOT appear as free-floating identifier
|
|
132
|
+
// suggestions. They are still used for tokenization and diagnostics suppression.
|
|
112
133
|
const builtin_names = BASE_BUILTINS
|
|
113
|
-
.concat(this._extraBuiltinNames)
|
|
114
134
|
.concat(this._dts.getGlobalNames());
|
|
115
135
|
|
|
116
136
|
this._completions = new CompletionEngine(this._analyzer, {
|
|
@@ -318,8 +338,9 @@ class RapydScriptLanguageService {
|
|
|
318
338
|
const new_names = this._dts.getGlobalNames();
|
|
319
339
|
// Suppress undef warnings for newly added globals
|
|
320
340
|
this._diagnostics.addGlobals(new_names);
|
|
321
|
-
// Rebuild builtin list so completions include new DTS names
|
|
322
|
-
|
|
341
|
+
// Rebuild builtin list so completions include new DTS names.
|
|
342
|
+
// _extraBuiltinNames are excluded — they are dot-completions only (see constructor).
|
|
343
|
+
const builtin_names = BASE_BUILTINS.concat(new_names);
|
|
323
344
|
this._completions = new CompletionEngine(this._analyzer, {
|
|
324
345
|
virtualFiles: this._virtualFiles,
|
|
325
346
|
stdlibFiles: this._stdlibFiles,
|
|
@@ -30,6 +30,9 @@ export class SymbolInfo {
|
|
|
30
30
|
this.inferred_class = opts.inferred_class || null; // 'ClassName' when x = ClassName(...)
|
|
31
31
|
this.dts_call_path = opts.dts_call_path || null; // 'ns.getServer' when x = ns.getServer(...)
|
|
32
32
|
this.type = null; // Phase 6: TypeInfo
|
|
33
|
+
this.return_type = opts.return_type || null; // annotated return type, e.g. 'list', 'str', 'MyClass'
|
|
34
|
+
this.source_module = opts.source_module || null; // module name when kind='import' (from X import Y)
|
|
35
|
+
this.original_name = opts.original_name || null; // pre-alias name for imports (from X import Y as Z → 'Y')
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
package/src/output/classes.pyj
CHANGED
|
@@ -214,7 +214,7 @@ def print_class(output):
|
|
|
214
214
|
output.end_statement()
|
|
215
215
|
|
|
216
216
|
elif is_node_type(stmt, AST_Class):
|
|
217
|
-
|
|
217
|
+
pass # nested classes are emitted in the statements section below
|
|
218
218
|
|
|
219
219
|
if not defined_methods['__repr__']:
|
|
220
220
|
define_default_method('__repr__', def():
|
|
@@ -271,9 +271,26 @@ def print_class(output):
|
|
|
271
271
|
output.print(JSON.stringify(create_doctring(self.docstrings)))
|
|
272
272
|
)
|
|
273
273
|
|
|
274
|
-
# Other statements in the class context
|
|
274
|
+
# Other statements in the class context (including nested class definitions)
|
|
275
275
|
for stmt in self.statements:
|
|
276
|
-
if
|
|
276
|
+
if is_node_type(stmt, AST_Class):
|
|
277
|
+
# Print the nested class in full, then attach it to the outer class.
|
|
278
|
+
# Two assignments mirror Python semantics:
|
|
279
|
+
# Outer.Inner — class-level access (Outer.Inner())
|
|
280
|
+
# Outer.prototype.Inner — instance-level access (self.Inner() inside methods)
|
|
281
|
+
output.indent()
|
|
282
|
+
stmt.print(output)
|
|
283
|
+
output.newline()
|
|
284
|
+
nested_name = stmt.name.name
|
|
285
|
+
output.indent()
|
|
286
|
+
self.name.print(output)
|
|
287
|
+
output.print('.' + nested_name + ' = ' + nested_name)
|
|
288
|
+
output.end_statement()
|
|
289
|
+
output.indent()
|
|
290
|
+
self.name.print(output)
|
|
291
|
+
output.print('.prototype.' + nested_name + ' = ' + nested_name)
|
|
292
|
+
output.end_statement()
|
|
293
|
+
elif not is_node_type(stmt, AST_Method):
|
|
277
294
|
output.indent()
|
|
278
295
|
stmt.print(output)
|
|
279
296
|
output.newline()
|
package/src/output/codegen.pyj
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# License: BSD Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
|
3
3
|
from __python__ import hash_literals
|
|
4
4
|
|
|
5
|
-
# globals:console,
|
|
5
|
+
# globals:console,writefile
|
|
6
6
|
|
|
7
7
|
from utils import noop
|
|
8
8
|
from parse import PRECEDENCE
|
|
@@ -279,7 +279,28 @@ def generate_code():
|
|
|
279
279
|
self._do_print(output, "return")
|
|
280
280
|
)
|
|
281
281
|
DEFPRINT(AST_Throw, def(self, output):
|
|
282
|
-
self.
|
|
282
|
+
if self.cause:
|
|
283
|
+
# raise X from Y: set __cause__ on exception before throwing
|
|
284
|
+
output.print('var ρσ_throw_tmp')
|
|
285
|
+
output.space()
|
|
286
|
+
output.print('=')
|
|
287
|
+
output.space()
|
|
288
|
+
self.value.print(output)
|
|
289
|
+
output.semicolon()
|
|
290
|
+
output.newline()
|
|
291
|
+
output.indent()
|
|
292
|
+
output.print('ρσ_throw_tmp.__cause__')
|
|
293
|
+
output.space()
|
|
294
|
+
output.print('=')
|
|
295
|
+
output.space()
|
|
296
|
+
self.cause.print(output)
|
|
297
|
+
output.semicolon()
|
|
298
|
+
output.newline()
|
|
299
|
+
output.indent()
|
|
300
|
+
output.print('throw ρσ_throw_tmp')
|
|
301
|
+
output.semicolon()
|
|
302
|
+
else:
|
|
303
|
+
self._do_print(output, "throw")
|
|
283
304
|
)
|
|
284
305
|
|
|
285
306
|
# -----[ loop control ]-----
|
|
@@ -292,6 +313,17 @@ def generate_code():
|
|
|
292
313
|
output.semicolon()
|
|
293
314
|
|
|
294
315
|
DEFPRINT(AST_Break, def(self, output):
|
|
316
|
+
# If we are directly inside a for/else labeled block and this break has
|
|
317
|
+
# no explicit label, redirect it to the forelse label so the else clause
|
|
318
|
+
# is skipped.
|
|
319
|
+
if not self.label and output.forelse_stack and output.forelse_stack.length:
|
|
320
|
+
top = output.forelse_stack[output.forelse_stack.length - 1]
|
|
321
|
+
if top:
|
|
322
|
+
output.print('break')
|
|
323
|
+
output.space()
|
|
324
|
+
output.print(top)
|
|
325
|
+
output.semicolon()
|
|
326
|
+
return
|
|
295
327
|
self._do_print(output, "break")
|
|
296
328
|
)
|
|
297
329
|
DEFPRINT(AST_Continue, def(self, output):
|
|
@@ -340,7 +372,10 @@ def generate_code():
|
|
|
340
372
|
DEFPRINT(AST_If, def(self, output):
|
|
341
373
|
output.print("if")
|
|
342
374
|
output.space()
|
|
343
|
-
|
|
375
|
+
if self.python_truthiness:
|
|
376
|
+
output.with_parens(def(): output.print('ρσ_bool('), self.condition.print(output), output.print(')');)
|
|
377
|
+
else:
|
|
378
|
+
output.with_parens(def(): self.condition.print(output);)
|
|
344
379
|
output.space()
|
|
345
380
|
if self.alternative:
|
|
346
381
|
make_then(self, output)
|