rapydscript-ns 0.8.4 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/.agignore +1 -1
  2. package/.github/workflows/ci.yml +38 -38
  3. package/=template.pyj +5 -5
  4. package/CHANGELOG.md +18 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +715 -169
  8. package/TODO.md +9 -2
  9. package/add-toc-to-readme +2 -2
  10. package/bin/export +75 -75
  11. package/bin/rapydscript +70 -70
  12. package/bin/web-repl-export +102 -102
  13. package/build +2 -2
  14. package/language-service/index.js +36 -27
  15. package/package.json +1 -1
  16. package/publish.py +37 -37
  17. package/release/baselib-plain-pretty.js +2358 -168
  18. package/release/baselib-plain-ugly.js +73 -3
  19. package/release/compiler.js +6282 -3092
  20. package/release/signatures.json +31 -30
  21. package/session.vim +4 -4
  22. package/setup.cfg +2 -2
  23. package/src/ast.pyj +1 -0
  24. package/src/baselib-builtins.pyj +340 -2
  25. package/src/baselib-bytes.pyj +664 -0
  26. package/src/baselib-errors.pyj +1 -1
  27. package/src/baselib-internal.pyj +267 -60
  28. package/src/baselib-itertools.pyj +110 -97
  29. package/src/baselib-str.pyj +22 -4
  30. package/src/compiler.pyj +36 -36
  31. package/src/errors.pyj +30 -30
  32. package/src/lib/abc.pyj +317 -0
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/copy.pyj +120 -120
  35. package/src/lib/dataclasses.pyj +532 -0
  36. package/src/lib/elementmaker.pyj +83 -83
  37. package/src/lib/encodings.pyj +126 -126
  38. package/src/lib/enum.pyj +125 -0
  39. package/src/lib/gettext.pyj +569 -569
  40. package/src/lib/itertools.pyj +580 -580
  41. package/src/lib/math.pyj +193 -193
  42. package/src/lib/operator.pyj +11 -11
  43. package/src/lib/pythonize.pyj +20 -20
  44. package/src/lib/random.pyj +118 -118
  45. package/src/lib/re.pyj +504 -470
  46. package/src/lib/react.pyj +74 -74
  47. package/src/lib/traceback.pyj +63 -63
  48. package/src/lib/typing.pyj +577 -0
  49. package/src/lib/uuid.pyj +77 -77
  50. package/src/monaco-language-service/builtins.js +14 -4
  51. package/src/monaco-language-service/diagnostics.js +19 -20
  52. package/src/monaco-language-service/dts.js +550 -550
  53. package/src/output/classes.pyj +62 -26
  54. package/src/output/comments.pyj +45 -45
  55. package/src/output/exceptions.pyj +201 -201
  56. package/src/output/functions.pyj +78 -5
  57. package/src/output/jsx.pyj +164 -164
  58. package/src/output/loops.pyj +5 -2
  59. package/src/output/operators.pyj +100 -34
  60. package/src/output/treeshake.pyj +182 -182
  61. package/src/output/utils.pyj +72 -72
  62. package/src/parse.pyj +80 -16
  63. package/src/string_interpolation.pyj +72 -72
  64. package/src/tokenizer.pyj +9 -4
  65. package/src/unicode_aliases.pyj +576 -576
  66. package/src/utils.pyj +192 -192
  67. package/test/_import_one.pyj +37 -37
  68. package/test/_import_two/__init__.pyj +11 -11
  69. package/test/_import_two/level2/deep.pyj +4 -4
  70. package/test/_import_two/other.pyj +6 -6
  71. package/test/_import_two/sub.pyj +13 -13
  72. package/test/abc.pyj +291 -0
  73. package/test/aes_vectors.pyj +421 -421
  74. package/test/annotations.pyj +80 -80
  75. package/test/arithmetic_nostrict.pyj +88 -0
  76. package/test/arithmetic_types.pyj +169 -0
  77. package/test/baselib.pyj +91 -0
  78. package/test/bytes.pyj +467 -0
  79. package/test/classes.pyj +1 -0
  80. package/test/comparison_ops.pyj +173 -0
  81. package/test/dataclasses.pyj +253 -0
  82. package/test/decorators.pyj +77 -77
  83. package/test/docstrings.pyj +39 -39
  84. package/test/elementmaker_test.pyj +45 -45
  85. package/test/enum.pyj +134 -0
  86. package/test/eval_exec.pyj +56 -0
  87. package/test/format.pyj +148 -0
  88. package/test/functions.pyj +151 -151
  89. package/test/generators.pyj +41 -41
  90. package/test/generic.pyj +370 -370
  91. package/test/imports.pyj +72 -72
  92. package/test/internationalization.pyj +73 -73
  93. package/test/lint.pyj +164 -164
  94. package/test/loops.pyj +85 -85
  95. package/test/numpy.pyj +734 -734
  96. package/test/object.pyj +64 -0
  97. package/test/omit_function_metadata.pyj +20 -20
  98. package/test/python_compat.pyj +17 -15
  99. package/test/python_features.pyj +70 -15
  100. package/test/regexp.pyj +83 -55
  101. package/test/repl.pyj +121 -121
  102. package/test/scoped_flags.pyj +76 -76
  103. package/test/tuples.pyj +96 -0
  104. package/test/typing.pyj +469 -0
  105. package/test/unit/index.js +116 -7
  106. package/test/unit/language-service-dts.js +543 -543
  107. package/test/unit/language-service-hover.js +455 -455
  108. package/test/unit/language-service.js +84 -0
  109. package/test/unit/web-repl.js +804 -1
  110. package/test/vars_locals_globals.pyj +94 -0
  111. package/tools/cli.js +558 -547
  112. package/tools/compile.js +224 -219
  113. package/tools/completer.js +131 -131
  114. package/tools/embedded_compiler.js +262 -251
  115. package/tools/gettext.js +185 -185
  116. package/tools/ini.js +65 -65
  117. package/tools/lint.js +16 -19
  118. package/tools/msgfmt.js +187 -187
  119. package/tools/repl.js +223 -223
  120. package/tools/test.js +118 -118
  121. package/tools/utils.js +128 -128
  122. package/tools/web_repl.js +95 -95
  123. package/try +41 -41
  124. package/web-repl/env.js +196 -196
  125. package/web-repl/index.html +163 -163
  126. package/web-repl/main.js +252 -252
  127. package/web-repl/prism.css +139 -139
  128. package/web-repl/prism.js +113 -113
  129. package/web-repl/rapydscript.js +224 -224
  130. package/web-repl/sha1.js +25 -25
  131. package/PYTHON_DIFFERENCES_REPORT.md +0 -291
  132. package/PYTHON_FEATURE_COVERAGE.md +0 -200
@@ -1,550 +1,550 @@
1
- // dts.js — Minimal .d.ts parser and type registry for the RapydScript language service.
2
- //
3
- // Parses a useful subset of TypeScript declaration syntax:
4
- // declare var / let / const name: Type;
5
- // declare function name(params): ReturnType;
6
- // declare class Name { ... }
7
- // interface Name { ... }
8
- // declare namespace Name { ... }
9
- // type Alias = Type;
10
- //
11
- // Usage:
12
- // import { DtsRegistry } from './dts.js';
13
- // const reg = new DtsRegistry();
14
- // reg.addDts('lib.dom', dtsText);
15
- // reg.getGlobalNames(); // string[]
16
- // reg.getGlobal('alert'); // TypeInfo | null
17
- // reg.getHoverMarkdown('alert'); // string | null
18
-
19
- // ---------------------------------------------------------------------------
20
- // TypeInfo
21
- // ---------------------------------------------------------------------------
22
-
23
- export class TypeInfo {
24
- /**
25
- * @param {object} opts
26
- * @param {string} opts.name
27
- * @param {string} opts.kind 'var'|'function'|'class'|'interface'|'namespace'|'method'|'property'
28
- * @param {Array|null} [opts.params] [{name,type,optional,rest}]
29
- * @param {string|null} [opts.return_type]
30
- * @param {Map|null} [opts.members] Map<string, TypeInfo>
31
- * @param {string|null} [opts.doc]
32
- * @param {string} [opts.source] 'dts'
33
- */
34
- constructor(opts) {
35
- this.name = opts.name;
36
- this.kind = opts.kind;
37
- this.params = opts.params || null;
38
- this.return_type = opts.return_type || null;
39
- this.members = opts.members || null;
40
- this.doc = opts.doc || null;
41
- this.source = opts.source || 'dts';
42
- }
43
- }
44
-
45
- // ---------------------------------------------------------------------------
46
- // Low-level helpers
47
- // ---------------------------------------------------------------------------
48
-
49
- /**
50
- * Find the index of the closing parenthesis that matches the opening one at
51
- * `start` in `str`. Returns -1 if not found.
52
- */
53
- function find_close_paren(str, start) {
54
- let depth = 0;
55
- for (let i = start; i < str.length; i++) {
56
- if (str[i] === '(') depth++;
57
- else if (str[i] === ')') { depth--; if (depth === 0) return i; }
58
- }
59
- return -1;
60
- }
61
-
62
- /**
63
- * Split a parameter list string on commas at depth 0, respecting
64
- * generic brackets `<>`, parens `()`, and square brackets `[]`.
65
- */
66
- function split_params(s) {
67
- const parts = [];
68
- let depth = 0;
69
- let cur = '';
70
- for (let i = 0; i < s.length; i++) {
71
- const ch = s[i];
72
- if (ch === '<' || ch === '(' || ch === '[' || ch === '{') depth++;
73
- else if (ch === '>' || ch === ')' || ch === ']' || ch === '}') depth--;
74
- else if (ch === ',' && depth === 0) {
75
- parts.push(cur.trim());
76
- cur = '';
77
- continue;
78
- }
79
- cur += ch;
80
- }
81
- if (cur.trim()) parts.push(cur.trim());
82
- return parts;
83
- }
84
-
85
- /**
86
- * Parse a single parameter token like `name: Type`, `name?: Type`,
87
- * `...name: Type[]` into `{name, type, optional, rest}`.
88
- */
89
- function parse_one_param(s) {
90
- const rest = s.startsWith('...');
91
- if (rest) s = s.slice(3);
92
-
93
- const colon = s.indexOf(':');
94
- let name, type;
95
- if (colon !== -1) {
96
- name = s.slice(0, colon).replace(/\?$/, '').trim();
97
- type = s.slice(colon + 1).trim();
98
- } else {
99
- name = s.replace(/\?$/, '').trim();
100
- type = 'any';
101
- }
102
- const optional = colon !== -1
103
- ? s.slice(0, colon).trimEnd().endsWith('?')
104
- : s.trimEnd().endsWith('?');
105
-
106
- return { name, type, optional, rest };
107
- }
108
-
109
- /**
110
- * Parse a `(...)` parameter section from a declaration string.
111
- * `decl` is the portion of text starting with `(`.
112
- * Returns `{ params, rest }` where `rest` is the text after the closing `)`.
113
- */
114
- function parse_params_from(decl) {
115
- if (!decl.startsWith('(')) return { params: [], rest: decl };
116
- const close = find_close_paren(decl, 0);
117
- if (close < 0) return { params: [], rest: '' };
118
- const inner = decl.slice(1, close);
119
- const rest = decl.slice(close + 1);
120
- const params = inner.trim()
121
- ? split_params(inner).map(parse_one_param)
122
- : [];
123
- return { params, rest };
124
- }
125
-
126
- /**
127
- * Extract the return type from text like `: ReturnType;` or `): ReturnType {`.
128
- * Returns null if no type annotation is found.
129
- */
130
- function parse_return_type(s) {
131
- const m = s.match(/^\s*:\s*(.+?)(?:\s*[;{]|$)/);
132
- return m ? m[1].trim() : null;
133
- }
134
-
135
- /**
136
- * Collect lines from a `/**...* /` block and return the trimmed doc text.
137
- */
138
- function extract_jsdoc(lines) {
139
- const text = lines
140
- .map(l => l
141
- .replace(/^\s*\/\*\*?\s?/, '')
142
- .replace(/\*\/$/, '')
143
- .replace(/^\s*\*\s?/, '')
144
- .trim()
145
- )
146
- .filter(l => l.length > 0)
147
- .join(' ');
148
- return text || null;
149
- }
150
-
151
- // ---------------------------------------------------------------------------
152
- // Block body collector
153
- // ---------------------------------------------------------------------------
154
-
155
- /**
156
- * Starting at line index `i`, collect all text up to and including the
157
- * matching closing `}`, tracking brace depth.
158
- * Returns `{ body, next_i }`.
159
- */
160
- function collect_block(lines, i) {
161
- const body_lines = [];
162
- let depth = 0;
163
- while (i < lines.length) {
164
- const l = lines[i];
165
- for (const ch of l) {
166
- if (ch === '{') depth++;
167
- else if (ch === '}') depth--;
168
- }
169
- body_lines.push(l);
170
- i++;
171
- if (depth === 0 && body_lines.length > 0) break;
172
- }
173
- const joined = body_lines.join('\n');
174
- const open = joined.indexOf('{');
175
- const close = joined.lastIndexOf('}');
176
- const inner = (open >= 0 && close > open) ? joined.slice(open + 1, close) : '';
177
- return { inner, next_i: i };
178
- }
179
-
180
- // ---------------------------------------------------------------------------
181
- // Member parser (inside class / interface / namespace bodies)
182
- // ---------------------------------------------------------------------------
183
-
184
- function parse_members(text) {
185
- const members = new Map();
186
- const lines = text.split('\n');
187
- let jsdoc_lines = [];
188
- let i = 0;
189
-
190
- while (i < lines.length) {
191
- const raw = lines[i];
192
- const line = raw.trim();
193
-
194
- // JSDoc block
195
- if (line.startsWith('/**') || (line.startsWith('/*') && !line.startsWith('*/')) ) {
196
- jsdoc_lines = [line];
197
- // If the block isn't closed on this line, keep reading
198
- if (!line.includes('*/')) {
199
- i++;
200
- while (i < lines.length && !lines[i].includes('*/')) {
201
- jsdoc_lines.push(lines[i].trim());
202
- i++;
203
- }
204
- if (i < lines.length) { jsdoc_lines.push(lines[i].trim()); i++; }
205
- } else {
206
- i++;
207
- }
208
- continue;
209
- }
210
-
211
- if (line.startsWith('//') || !line) {
212
- if (!line) jsdoc_lines = [];
213
- i++;
214
- continue;
215
- }
216
-
217
- const doc = extract_jsdoc(jsdoc_lines);
218
- jsdoc_lines = [];
219
-
220
- // Strip access modifiers and declaration keywords
221
- let l = line.replace(
222
- /^(?:static\s+|readonly\s+|abstract\s+|override\s+|protected\s+|private\s+|public\s+)*/,
223
- ''
224
- ).trim();
225
- // Inside namespaces, members can be prefixed with function/const/let/var
226
- const fn_kw = l.match(/^function\s+/);
227
- if (fn_kw) l = l.slice(fn_kw[0].length).trim();
228
- const var_kw = l.match(/^(?:const|let|var)\s+/);
229
- if (var_kw) l = l.slice(var_kw[0].length).trim();
230
-
231
- // Skip index signatures [key: Type]: Type
232
- if (l.startsWith('[')) { i++; continue; }
233
-
234
- // Skip nested blocks (e.g. nested namespace)
235
- if (/^(?:class|interface|namespace|module)\s/.test(l)) { i++; continue; }
236
-
237
- // method(...): ReturnType; or method(...) {
238
- const paren_idx = l.indexOf('(');
239
- if (paren_idx > 0) {
240
- const name = l.slice(0, paren_idx).replace(/[?<].*$/, '').trim();
241
- if (name && /^\w+$/.test(name) && name !== 'constructor') {
242
- const after_name = l.slice(paren_idx);
243
- const { params, rest } = parse_params_from(after_name);
244
- const return_type = parse_return_type(rest);
245
- members.set(name, new TypeInfo({
246
- name,
247
- kind: 'method',
248
- params,
249
- return_type,
250
- doc,
251
- }));
252
- }
253
- i++;
254
- continue;
255
- }
256
-
257
- // property: Type;
258
- const prop_m = l.match(/^(\w+)\??\s*:\s*(.+?)\s*[;,]?$/);
259
- if (prop_m) {
260
- members.set(prop_m[1], new TypeInfo({
261
- name: prop_m[1],
262
- kind: 'property',
263
- return_type: prop_m[2],
264
- doc,
265
- }));
266
- i++;
267
- continue;
268
- }
269
-
270
- i++;
271
- }
272
-
273
- return members;
274
- }
275
-
276
- // ---------------------------------------------------------------------------
277
- // Top-level parser
278
- // ---------------------------------------------------------------------------
279
-
280
- /**
281
- * Parse a .d.ts file and return an array of TypeInfo objects (top-level only).
282
- * @param {string} text
283
- * @returns {TypeInfo[]}
284
- */
285
- export function parse_dts(text) {
286
- const lines = text.split('\n');
287
- const results = [];
288
- let jsdoc_lines = [];
289
- let i = 0;
290
-
291
- while (i < lines.length) {
292
- const raw = lines[i];
293
- const line = raw.trim();
294
-
295
- // Accumulate JSDoc
296
- if (line.startsWith('/**') || (line.startsWith('/*') && !line.startsWith('*/'))) {
297
- jsdoc_lines = [line];
298
- if (!line.includes('*/')) {
299
- i++;
300
- while (i < lines.length && !lines[i].includes('*/')) {
301
- jsdoc_lines.push(lines[i].trim());
302
- i++;
303
- }
304
- if (i < lines.length) { jsdoc_lines.push(lines[i].trim()); i++; }
305
- } else {
306
- i++;
307
- }
308
- continue;
309
- }
310
-
311
- if (line.startsWith('//') || !line) {
312
- if (!line) jsdoc_lines = [];
313
- i++;
314
- continue;
315
- }
316
-
317
- const doc = extract_jsdoc(jsdoc_lines);
318
- jsdoc_lines = [];
319
-
320
- // Strip `export` and `declare` prefixes
321
- let l = line
322
- .replace(/^export\s+default\s+/, '')
323
- .replace(/^export\s+/, '')
324
- .replace(/^declare\s+/, '')
325
- .trim();
326
-
327
- // ── var / let / const ──────────────────────────────────────────────
328
- const var_m = l.match(/^(?:var|let|const)\s+(\w+)\s*(?::\s*(.+?))?\s*[;=]/);
329
- if (var_m) {
330
- results.push(new TypeInfo({
331
- name: var_m[1],
332
- kind: 'var',
333
- return_type: var_m[2] ? var_m[2].trim() : null,
334
- doc,
335
- }));
336
- i++;
337
- continue;
338
- }
339
-
340
- // ── function ───────────────────────────────────────────────────────
341
- const fn_paren = l.indexOf('(');
342
- const fn_m = fn_paren > 0 && l.match(/^function\s+(\w+)/);
343
- if (fn_m) {
344
- const name = fn_m[1];
345
- const after_name = l.slice(l.indexOf('('));
346
- const { params, rest } = parse_params_from(after_name);
347
- const return_type = parse_return_type(rest);
348
- results.push(new TypeInfo({ name, kind: 'function', params, return_type, doc }));
349
- i++;
350
- continue;
351
- }
352
-
353
- // ── class / interface / abstract class / namespace / module ────────
354
- const block_m = l.match(/^(abstract\s+class|class|interface|namespace|module)\s+(\w+)/);
355
- if (block_m) {
356
- const kind_raw = block_m[1].replace('abstract ', '').trim();
357
- const kind = (kind_raw === 'module') ? 'namespace' : kind_raw;
358
- const name = block_m[2];
359
-
360
- if (l.includes('{')) {
361
- // Block opens on this line — collect to matching '}'
362
- const { inner, next_i } = collect_block(lines, i);
363
- const members = parse_members(inner);
364
- results.push(new TypeInfo({ name, kind, members, doc }));
365
- i = next_i;
366
- } else {
367
- // Declaration without body (e.g. `declare class Foo;`)
368
- results.push(new TypeInfo({ name, kind, doc }));
369
- i++;
370
- }
371
- continue;
372
- }
373
-
374
- // ── type alias (no body extracted — just register the name) ────────
375
- const type_m = l.match(/^type\s+(\w+)\s*(?:<[^>]*>)?\s*=/);
376
- if (type_m) {
377
- results.push(new TypeInfo({ name: type_m[1], kind: 'var', doc }));
378
- i++;
379
- continue;
380
- }
381
-
382
- i++;
383
- jsdoc_lines = [];
384
- }
385
-
386
- return results;
387
- }
388
-
389
- // ---------------------------------------------------------------------------
390
- // Type-string helpers
391
- // ---------------------------------------------------------------------------
392
-
393
- /**
394
- * Extract the first concrete type name from a TypeScript type string.
395
- * Handles union types (A | B), generic types (A<B>), and simple names.
396
- * Returns null if no plain identifier can be extracted.
397
- *
398
- * Examples:
399
- * 'Server' → 'Server'
400
- * 'Server | (DarkServer & { isOnline: boolean })' → 'Server'
401
- * 'Promise<number>' → 'Promise'
402
- */
403
- export function resolve_first_type(type_str) {
404
- if (!type_str) return null;
405
- const first = type_str.split('|')[0].trim();
406
- const base = first.replace(/<.*$/, '').trim();
407
- return /^\w+$/.test(base) ? base : null;
408
- }
409
-
410
- // ---------------------------------------------------------------------------
411
- // DtsRegistry
412
- // ---------------------------------------------------------------------------
413
-
414
- export class DtsRegistry {
415
- constructor() {
416
- this._globals = new Map(); // name → TypeInfo
417
- }
418
-
419
- /**
420
- * Parse and register a .d.ts file. Calling this multiple times merges
421
- * all declarations into the same global namespace.
422
- * @param {string} _name a label for this file (reserved for future dedup)
423
- * @param {string} text the .d.ts source text
424
- */
425
- addDts(_name, text) {
426
- const types = parse_dts(text);
427
- for (const ti of types) {
428
- this._globals.set(ti.name, ti);
429
- }
430
- }
431
-
432
- /**
433
- * Return all registered global symbol names.
434
- * @returns {string[]}
435
- */
436
- getGlobalNames() {
437
- return Array.from(this._globals.keys());
438
- }
439
-
440
- /**
441
- * Return the TypeInfo for a name, or null.
442
- * @param {string} name
443
- * @returns {TypeInfo|null}
444
- */
445
- getGlobal(name) {
446
- return this._globals.get(name) || null;
447
- }
448
-
449
- /**
450
- * Return a Monaco hover markdown string for a name, or null.
451
- * @param {string} name
452
- * @returns {string|null}
453
- */
454
- getHoverMarkdown(name) {
455
- const ti = this.getGlobal(name);
456
- return ti ? build_dts_hover(ti) : null;
457
- }
458
-
459
- /**
460
- * Return completion-style member names for a type (used for dot-completion
461
- * on .d.ts-typed variables when Phase 6 type inference is wired up).
462
- * @param {string} name
463
- * @returns {string[]}
464
- */
465
- getMemberNames(name) {
466
- const ti = this.getGlobal(name);
467
- if (!ti || !ti.members) return [];
468
- return Array.from(ti.members.keys());
469
- }
470
-
471
- /**
472
- * Follow a dot-path through the type registry and return the TypeInfo
473
- * for the named member at the end of the chain.
474
- *
475
- * Examples:
476
- * getMemberInfo('ns', 'hack') → TypeInfo for NS.hack
477
- * getMemberInfo('ns.hacknet', 'purchaseNode') → TypeInfo for Hacknet.purchaseNode
478
- *
479
- * @param {string} objectPath dot-separated path (e.g. 'ns' or 'ns.hacknet')
480
- * @param {string} memberName the attribute being accessed
481
- * @returns {TypeInfo|null}
482
- */
483
- getMemberInfo(objectPath, memberName) {
484
- const parts = objectPath.split('.');
485
- let ti = this._globals.get(parts[0]);
486
- // Dereference var → type (e.g. var ns: NS → NS interface)
487
- if (ti && !ti.members && ti.return_type) {
488
- ti = this._globals.get(resolve_first_type(ti.return_type));
489
- }
490
- // Walk remaining path segments
491
- for (let i = 1; i < parts.length && ti; i++) {
492
- const member = ti.members ? ti.members.get(parts[i]) : null;
493
- if (!member) { ti = null; break; }
494
- if (member.members) {
495
- ti = member;
496
- } else if (member.return_type) {
497
- ti = this._globals.get(resolve_first_type(member.return_type));
498
- } else {
499
- ti = null;
500
- }
501
- }
502
- if (!ti || !ti.members) return null;
503
- return ti.members.get(memberName) || null;
504
- }
505
-
506
- /**
507
- * Return hover markdown for a member accessed via a dot-path, or null.
508
- * @param {string} objectPath
509
- * @param {string} memberName
510
- * @returns {string|null}
511
- */
512
- getMemberHoverMarkdown(objectPath, memberName) {
513
- const ti = this.getMemberInfo(objectPath, memberName);
514
- return ti ? build_dts_hover(ti) : null;
515
- }
516
- }
517
-
518
- // ---------------------------------------------------------------------------
519
- // DTS hover rendering
520
- // ---------------------------------------------------------------------------
521
-
522
- function build_dts_sig(ti) {
523
- if (ti.kind === 'function' || ti.kind === 'method') {
524
- const param_str = ti.params
525
- ? ti.params.map(function (p) {
526
- let s = p.rest ? '...' : '';
527
- s += p.name;
528
- if (p.optional) s += '?';
529
- if (p.type && p.type !== 'any') s += ': ' + p.type;
530
- return s;
531
- }).join(', ')
532
- : '';
533
- let sig = '(' + ti.kind + ') ' + ti.name + '(' + param_str + ')';
534
- if (ti.return_type && ti.return_type !== 'void') sig += ': ' + ti.return_type;
535
- return sig;
536
- }
537
- if (ti.kind === 'property' || ti.kind === 'var') {
538
- let sig = '(' + ti.kind + ') ' + ti.name;
539
- if (ti.return_type) sig += ': ' + ti.return_type;
540
- return sig;
541
- }
542
- return '(' + ti.kind + ') ' + ti.name;
543
- }
544
-
545
- function build_dts_hover(ti) {
546
- const sig = build_dts_sig(ti);
547
- let md = '```typescript\n' + sig + '\n```';
548
- if (ti.doc) md += '\n\n' + ti.doc;
549
- return md;
550
- }
1
+ // dts.js — Minimal .d.ts parser and type registry for the RapydScript language service.
2
+ //
3
+ // Parses a useful subset of TypeScript declaration syntax:
4
+ // declare var / let / const name: Type;
5
+ // declare function name(params): ReturnType;
6
+ // declare class Name { ... }
7
+ // interface Name { ... }
8
+ // declare namespace Name { ... }
9
+ // type Alias = Type;
10
+ //
11
+ // Usage:
12
+ // import { DtsRegistry } from './dts.js';
13
+ // const reg = new DtsRegistry();
14
+ // reg.addDts('lib.dom', dtsText);
15
+ // reg.getGlobalNames(); // string[]
16
+ // reg.getGlobal('alert'); // TypeInfo | null
17
+ // reg.getHoverMarkdown('alert'); // string | null
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // TypeInfo
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export class TypeInfo {
24
+ /**
25
+ * @param {object} opts
26
+ * @param {string} opts.name
27
+ * @param {string} opts.kind 'var'|'function'|'class'|'interface'|'namespace'|'method'|'property'
28
+ * @param {Array|null} [opts.params] [{name,type,optional,rest}]
29
+ * @param {string|null} [opts.return_type]
30
+ * @param {Map|null} [opts.members] Map<string, TypeInfo>
31
+ * @param {string|null} [opts.doc]
32
+ * @param {string} [opts.source] 'dts'
33
+ */
34
+ constructor(opts) {
35
+ this.name = opts.name;
36
+ this.kind = opts.kind;
37
+ this.params = opts.params || null;
38
+ this.return_type = opts.return_type || null;
39
+ this.members = opts.members || null;
40
+ this.doc = opts.doc || null;
41
+ this.source = opts.source || 'dts';
42
+ }
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // Low-level helpers
47
+ // ---------------------------------------------------------------------------
48
+
49
+ /**
50
+ * Find the index of the closing parenthesis that matches the opening one at
51
+ * `start` in `str`. Returns -1 if not found.
52
+ */
53
+ function find_close_paren(str, start) {
54
+ let depth = 0;
55
+ for (let i = start; i < str.length; i++) {
56
+ if (str[i] === '(') depth++;
57
+ else if (str[i] === ')') { depth--; if (depth === 0) return i; }
58
+ }
59
+ return -1;
60
+ }
61
+
62
+ /**
63
+ * Split a parameter list string on commas at depth 0, respecting
64
+ * generic brackets `<>`, parens `()`, and square brackets `[]`.
65
+ */
66
+ function split_params(s) {
67
+ const parts = [];
68
+ let depth = 0;
69
+ let cur = '';
70
+ for (let i = 0; i < s.length; i++) {
71
+ const ch = s[i];
72
+ if (ch === '<' || ch === '(' || ch === '[' || ch === '{') depth++;
73
+ else if (ch === '>' || ch === ')' || ch === ']' || ch === '}') depth--;
74
+ else if (ch === ',' && depth === 0) {
75
+ parts.push(cur.trim());
76
+ cur = '';
77
+ continue;
78
+ }
79
+ cur += ch;
80
+ }
81
+ if (cur.trim()) parts.push(cur.trim());
82
+ return parts;
83
+ }
84
+
85
+ /**
86
+ * Parse a single parameter token like `name: Type`, `name?: Type`,
87
+ * `...name: Type[]` into `{name, type, optional, rest}`.
88
+ */
89
+ function parse_one_param(s) {
90
+ const rest = s.startsWith('...');
91
+ if (rest) s = s.slice(3);
92
+
93
+ const colon = s.indexOf(':');
94
+ let name, type;
95
+ if (colon !== -1) {
96
+ name = s.slice(0, colon).replace(/\?$/, '').trim();
97
+ type = s.slice(colon + 1).trim();
98
+ } else {
99
+ name = s.replace(/\?$/, '').trim();
100
+ type = 'any';
101
+ }
102
+ const optional = colon !== -1
103
+ ? s.slice(0, colon).trimEnd().endsWith('?')
104
+ : s.trimEnd().endsWith('?');
105
+
106
+ return { name, type, optional, rest };
107
+ }
108
+
109
+ /**
110
+ * Parse a `(...)` parameter section from a declaration string.
111
+ * `decl` is the portion of text starting with `(`.
112
+ * Returns `{ params, rest }` where `rest` is the text after the closing `)`.
113
+ */
114
+ function parse_params_from(decl) {
115
+ if (!decl.startsWith('(')) return { params: [], rest: decl };
116
+ const close = find_close_paren(decl, 0);
117
+ if (close < 0) return { params: [], rest: '' };
118
+ const inner = decl.slice(1, close);
119
+ const rest = decl.slice(close + 1);
120
+ const params = inner.trim()
121
+ ? split_params(inner).map(parse_one_param)
122
+ : [];
123
+ return { params, rest };
124
+ }
125
+
126
+ /**
127
+ * Extract the return type from text like `: ReturnType;` or `): ReturnType {`.
128
+ * Returns null if no type annotation is found.
129
+ */
130
+ function parse_return_type(s) {
131
+ const m = s.match(/^\s*:\s*(.+?)(?:\s*[;{]|$)/);
132
+ return m ? m[1].trim() : null;
133
+ }
134
+
135
+ /**
136
+ * Collect lines from a `/**...* /` block and return the trimmed doc text.
137
+ */
138
+ function extract_jsdoc(lines) {
139
+ const text = lines
140
+ .map(l => l
141
+ .replace(/^\s*\/\*\*?\s?/, '')
142
+ .replace(/\*\/$/, '')
143
+ .replace(/^\s*\*\s?/, '')
144
+ .trim()
145
+ )
146
+ .filter(l => l.length > 0)
147
+ .join(' ');
148
+ return text || null;
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // Block body collector
153
+ // ---------------------------------------------------------------------------
154
+
155
+ /**
156
+ * Starting at line index `i`, collect all text up to and including the
157
+ * matching closing `}`, tracking brace depth.
158
+ * Returns `{ body, next_i }`.
159
+ */
160
+ function collect_block(lines, i) {
161
+ const body_lines = [];
162
+ let depth = 0;
163
+ while (i < lines.length) {
164
+ const l = lines[i];
165
+ for (const ch of l) {
166
+ if (ch === '{') depth++;
167
+ else if (ch === '}') depth--;
168
+ }
169
+ body_lines.push(l);
170
+ i++;
171
+ if (depth === 0 && body_lines.length > 0) break;
172
+ }
173
+ const joined = body_lines.join('\n');
174
+ const open = joined.indexOf('{');
175
+ const close = joined.lastIndexOf('}');
176
+ const inner = (open >= 0 && close > open) ? joined.slice(open + 1, close) : '';
177
+ return { inner, next_i: i };
178
+ }
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // Member parser (inside class / interface / namespace bodies)
182
+ // ---------------------------------------------------------------------------
183
+
184
+ function parse_members(text) {
185
+ const members = new Map();
186
+ const lines = text.split('\n');
187
+ let jsdoc_lines = [];
188
+ let i = 0;
189
+
190
+ while (i < lines.length) {
191
+ const raw = lines[i];
192
+ const line = raw.trim();
193
+
194
+ // JSDoc block
195
+ if (line.startsWith('/**') || (line.startsWith('/*') && !line.startsWith('*/')) ) {
196
+ jsdoc_lines = [line];
197
+ // If the block isn't closed on this line, keep reading
198
+ if (!line.includes('*/')) {
199
+ i++;
200
+ while (i < lines.length && !lines[i].includes('*/')) {
201
+ jsdoc_lines.push(lines[i].trim());
202
+ i++;
203
+ }
204
+ if (i < lines.length) { jsdoc_lines.push(lines[i].trim()); i++; }
205
+ } else {
206
+ i++;
207
+ }
208
+ continue;
209
+ }
210
+
211
+ if (line.startsWith('//') || !line) {
212
+ if (!line) jsdoc_lines = [];
213
+ i++;
214
+ continue;
215
+ }
216
+
217
+ const doc = extract_jsdoc(jsdoc_lines);
218
+ jsdoc_lines = [];
219
+
220
+ // Strip access modifiers and declaration keywords
221
+ let l = line.replace(
222
+ /^(?:static\s+|readonly\s+|abstract\s+|override\s+|protected\s+|private\s+|public\s+)*/,
223
+ ''
224
+ ).trim();
225
+ // Inside namespaces, members can be prefixed with function/const/let/var
226
+ const fn_kw = l.match(/^function\s+/);
227
+ if (fn_kw) l = l.slice(fn_kw[0].length).trim();
228
+ const var_kw = l.match(/^(?:const|let|var)\s+/);
229
+ if (var_kw) l = l.slice(var_kw[0].length).trim();
230
+
231
+ // Skip index signatures [key: Type]: Type
232
+ if (l.startsWith('[')) { i++; continue; }
233
+
234
+ // Skip nested blocks (e.g. nested namespace)
235
+ if (/^(?:class|interface|namespace|module)\s/.test(l)) { i++; continue; }
236
+
237
+ // method(...): ReturnType; or method(...) {
238
+ const paren_idx = l.indexOf('(');
239
+ if (paren_idx > 0) {
240
+ const name = l.slice(0, paren_idx).replace(/[?<].*$/, '').trim();
241
+ if (name && /^\w+$/.test(name) && name !== 'constructor') {
242
+ const after_name = l.slice(paren_idx);
243
+ const { params, rest } = parse_params_from(after_name);
244
+ const return_type = parse_return_type(rest);
245
+ members.set(name, new TypeInfo({
246
+ name,
247
+ kind: 'method',
248
+ params,
249
+ return_type,
250
+ doc,
251
+ }));
252
+ }
253
+ i++;
254
+ continue;
255
+ }
256
+
257
+ // property: Type;
258
+ const prop_m = l.match(/^(\w+)\??\s*:\s*(.+?)\s*[;,]?$/);
259
+ if (prop_m) {
260
+ members.set(prop_m[1], new TypeInfo({
261
+ name: prop_m[1],
262
+ kind: 'property',
263
+ return_type: prop_m[2],
264
+ doc,
265
+ }));
266
+ i++;
267
+ continue;
268
+ }
269
+
270
+ i++;
271
+ }
272
+
273
+ return members;
274
+ }
275
+
276
+ // ---------------------------------------------------------------------------
277
+ // Top-level parser
278
+ // ---------------------------------------------------------------------------
279
+
280
+ /**
281
+ * Parse a .d.ts file and return an array of TypeInfo objects (top-level only).
282
+ * @param {string} text
283
+ * @returns {TypeInfo[]}
284
+ */
285
+ export function parse_dts(text) {
286
+ const lines = text.split('\n');
287
+ const results = [];
288
+ let jsdoc_lines = [];
289
+ let i = 0;
290
+
291
+ while (i < lines.length) {
292
+ const raw = lines[i];
293
+ const line = raw.trim();
294
+
295
+ // Accumulate JSDoc
296
+ if (line.startsWith('/**') || (line.startsWith('/*') && !line.startsWith('*/'))) {
297
+ jsdoc_lines = [line];
298
+ if (!line.includes('*/')) {
299
+ i++;
300
+ while (i < lines.length && !lines[i].includes('*/')) {
301
+ jsdoc_lines.push(lines[i].trim());
302
+ i++;
303
+ }
304
+ if (i < lines.length) { jsdoc_lines.push(lines[i].trim()); i++; }
305
+ } else {
306
+ i++;
307
+ }
308
+ continue;
309
+ }
310
+
311
+ if (line.startsWith('//') || !line) {
312
+ if (!line) jsdoc_lines = [];
313
+ i++;
314
+ continue;
315
+ }
316
+
317
+ const doc = extract_jsdoc(jsdoc_lines);
318
+ jsdoc_lines = [];
319
+
320
+ // Strip `export` and `declare` prefixes
321
+ let l = line
322
+ .replace(/^export\s+default\s+/, '')
323
+ .replace(/^export\s+/, '')
324
+ .replace(/^declare\s+/, '')
325
+ .trim();
326
+
327
+ // ── var / let / const ──────────────────────────────────────────────
328
+ const var_m = l.match(/^(?:var|let|const)\s+(\w+)\s*(?::\s*(.+?))?\s*[;=]/);
329
+ if (var_m) {
330
+ results.push(new TypeInfo({
331
+ name: var_m[1],
332
+ kind: 'var',
333
+ return_type: var_m[2] ? var_m[2].trim() : null,
334
+ doc,
335
+ }));
336
+ i++;
337
+ continue;
338
+ }
339
+
340
+ // ── function ───────────────────────────────────────────────────────
341
+ const fn_paren = l.indexOf('(');
342
+ const fn_m = fn_paren > 0 && l.match(/^function\s+(\w+)/);
343
+ if (fn_m) {
344
+ const name = fn_m[1];
345
+ const after_name = l.slice(l.indexOf('('));
346
+ const { params, rest } = parse_params_from(after_name);
347
+ const return_type = parse_return_type(rest);
348
+ results.push(new TypeInfo({ name, kind: 'function', params, return_type, doc }));
349
+ i++;
350
+ continue;
351
+ }
352
+
353
+ // ── class / interface / abstract class / namespace / module ────────
354
+ const block_m = l.match(/^(abstract\s+class|class|interface|namespace|module)\s+(\w+)/);
355
+ if (block_m) {
356
+ const kind_raw = block_m[1].replace('abstract ', '').trim();
357
+ const kind = (kind_raw === 'module') ? 'namespace' : kind_raw;
358
+ const name = block_m[2];
359
+
360
+ if (l.includes('{')) {
361
+ // Block opens on this line — collect to matching '}'
362
+ const { inner, next_i } = collect_block(lines, i);
363
+ const members = parse_members(inner);
364
+ results.push(new TypeInfo({ name, kind, members, doc }));
365
+ i = next_i;
366
+ } else {
367
+ // Declaration without body (e.g. `declare class Foo;`)
368
+ results.push(new TypeInfo({ name, kind, doc }));
369
+ i++;
370
+ }
371
+ continue;
372
+ }
373
+
374
+ // ── type alias (no body extracted — just register the name) ────────
375
+ const type_m = l.match(/^type\s+(\w+)\s*(?:<[^>]*>)?\s*=/);
376
+ if (type_m) {
377
+ results.push(new TypeInfo({ name: type_m[1], kind: 'var', doc }));
378
+ i++;
379
+ continue;
380
+ }
381
+
382
+ i++;
383
+ jsdoc_lines = [];
384
+ }
385
+
386
+ return results;
387
+ }
388
+
389
+ // ---------------------------------------------------------------------------
390
+ // Type-string helpers
391
+ // ---------------------------------------------------------------------------
392
+
393
+ /**
394
+ * Extract the first concrete type name from a TypeScript type string.
395
+ * Handles union types (A | B), generic types (A<B>), and simple names.
396
+ * Returns null if no plain identifier can be extracted.
397
+ *
398
+ * Examples:
399
+ * 'Server' → 'Server'
400
+ * 'Server | (DarkServer & { isOnline: boolean })' → 'Server'
401
+ * 'Promise<number>' → 'Promise'
402
+ */
403
+ export function resolve_first_type(type_str) {
404
+ if (!type_str) return null;
405
+ const first = type_str.split('|')[0].trim();
406
+ const base = first.replace(/<.*$/, '').trim();
407
+ return /^\w+$/.test(base) ? base : null;
408
+ }
409
+
410
+ // ---------------------------------------------------------------------------
411
+ // DtsRegistry
412
+ // ---------------------------------------------------------------------------
413
+
414
+ export class DtsRegistry {
415
+ constructor() {
416
+ this._globals = new Map(); // name → TypeInfo
417
+ }
418
+
419
+ /**
420
+ * Parse and register a .d.ts file. Calling this multiple times merges
421
+ * all declarations into the same global namespace.
422
+ * @param {string} _name a label for this file (reserved for future dedup)
423
+ * @param {string} text the .d.ts source text
424
+ */
425
+ addDts(_name, text) {
426
+ const types = parse_dts(text);
427
+ for (const ti of types) {
428
+ this._globals.set(ti.name, ti);
429
+ }
430
+ }
431
+
432
+ /**
433
+ * Return all registered global symbol names.
434
+ * @returns {string[]}
435
+ */
436
+ getGlobalNames() {
437
+ return Array.from(this._globals.keys());
438
+ }
439
+
440
+ /**
441
+ * Return the TypeInfo for a name, or null.
442
+ * @param {string} name
443
+ * @returns {TypeInfo|null}
444
+ */
445
+ getGlobal(name) {
446
+ return this._globals.get(name) || null;
447
+ }
448
+
449
+ /**
450
+ * Return a Monaco hover markdown string for a name, or null.
451
+ * @param {string} name
452
+ * @returns {string|null}
453
+ */
454
+ getHoverMarkdown(name) {
455
+ const ti = this.getGlobal(name);
456
+ return ti ? build_dts_hover(ti) : null;
457
+ }
458
+
459
+ /**
460
+ * Return completion-style member names for a type (used for dot-completion
461
+ * on .d.ts-typed variables when Phase 6 type inference is wired up).
462
+ * @param {string} name
463
+ * @returns {string[]}
464
+ */
465
+ getMemberNames(name) {
466
+ const ti = this.getGlobal(name);
467
+ if (!ti || !ti.members) return [];
468
+ return Array.from(ti.members.keys());
469
+ }
470
+
471
+ /**
472
+ * Follow a dot-path through the type registry and return the TypeInfo
473
+ * for the named member at the end of the chain.
474
+ *
475
+ * Examples:
476
+ * getMemberInfo('ns', 'hack') → TypeInfo for NS.hack
477
+ * getMemberInfo('ns.hacknet', 'purchaseNode') → TypeInfo for Hacknet.purchaseNode
478
+ *
479
+ * @param {string} objectPath dot-separated path (e.g. 'ns' or 'ns.hacknet')
480
+ * @param {string} memberName the attribute being accessed
481
+ * @returns {TypeInfo|null}
482
+ */
483
+ getMemberInfo(objectPath, memberName) {
484
+ const parts = objectPath.split('.');
485
+ let ti = this._globals.get(parts[0]);
486
+ // Dereference var → type (e.g. var ns: NS → NS interface)
487
+ if (ti && !ti.members && ti.return_type) {
488
+ ti = this._globals.get(resolve_first_type(ti.return_type));
489
+ }
490
+ // Walk remaining path segments
491
+ for (let i = 1; i < parts.length && ti; i++) {
492
+ const member = ti.members ? ti.members.get(parts[i]) : null;
493
+ if (!member) { ti = null; break; }
494
+ if (member.members) {
495
+ ti = member;
496
+ } else if (member.return_type) {
497
+ ti = this._globals.get(resolve_first_type(member.return_type));
498
+ } else {
499
+ ti = null;
500
+ }
501
+ }
502
+ if (!ti || !ti.members) return null;
503
+ return ti.members.get(memberName) || null;
504
+ }
505
+
506
+ /**
507
+ * Return hover markdown for a member accessed via a dot-path, or null.
508
+ * @param {string} objectPath
509
+ * @param {string} memberName
510
+ * @returns {string|null}
511
+ */
512
+ getMemberHoverMarkdown(objectPath, memberName) {
513
+ const ti = this.getMemberInfo(objectPath, memberName);
514
+ return ti ? build_dts_hover(ti) : null;
515
+ }
516
+ }
517
+
518
+ // ---------------------------------------------------------------------------
519
+ // DTS hover rendering
520
+ // ---------------------------------------------------------------------------
521
+
522
+ function build_dts_sig(ti) {
523
+ if (ti.kind === 'function' || ti.kind === 'method') {
524
+ const param_str = ti.params
525
+ ? ti.params.map(function (p) {
526
+ let s = p.rest ? '...' : '';
527
+ s += p.name;
528
+ if (p.optional) s += '?';
529
+ if (p.type && p.type !== 'any') s += ': ' + p.type;
530
+ return s;
531
+ }).join(', ')
532
+ : '';
533
+ let sig = '(' + ti.kind + ') ' + ti.name + '(' + param_str + ')';
534
+ if (ti.return_type && ti.return_type !== 'void') sig += ': ' + ti.return_type;
535
+ return sig;
536
+ }
537
+ if (ti.kind === 'property' || ti.kind === 'var') {
538
+ let sig = '(' + ti.kind + ') ' + ti.name;
539
+ if (ti.return_type) sig += ': ' + ti.return_type;
540
+ return sig;
541
+ }
542
+ return '(' + ti.kind + ') ' + ti.name;
543
+ }
544
+
545
+ function build_dts_hover(ti) {
546
+ const sig = build_dts_sig(ti);
547
+ let md = '```typescript\n' + sig + '\n```';
548
+ if (ti.doc) md += '\n\n' + ti.doc;
549
+ return md;
550
+ }