rapydscript-ns 0.9.0 → 0.9.2

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 (100) 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 +10 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/README.md +7 -6
  8. package/TODO.md +116 -1
  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 +9 -9
  15. package/language-service/language-service.d.ts +1 -1
  16. package/package.json +6 -2
  17. package/publish.py +37 -37
  18. package/release/compiler.js +246 -231
  19. package/release/signatures.json +23 -23
  20. package/session.vim +4 -4
  21. package/setup.cfg +2 -2
  22. package/src/compiler.pyj +36 -36
  23. package/src/errors.pyj +30 -30
  24. package/src/lib/aes.pyj +646 -646
  25. package/src/lib/contextlib.pyj +379 -0
  26. package/src/lib/copy.pyj +120 -120
  27. package/src/lib/datetime.pyj +712 -0
  28. package/src/lib/elementmaker.pyj +83 -83
  29. package/src/lib/encodings.pyj +126 -126
  30. package/src/lib/gettext.pyj +569 -569
  31. package/src/lib/io.pyj +500 -0
  32. package/src/lib/itertools.pyj +580 -580
  33. package/src/lib/json.pyj +227 -0
  34. package/src/lib/math.pyj +193 -193
  35. package/src/lib/operator.pyj +11 -11
  36. package/src/lib/pythonize.pyj +20 -20
  37. package/src/lib/random.pyj +118 -118
  38. package/src/lib/react.pyj +74 -74
  39. package/src/lib/traceback.pyj +63 -63
  40. package/src/lib/uuid.pyj +77 -77
  41. package/src/monaco-language-service/diagnostics.js +4 -4
  42. package/src/monaco-language-service/dts.js +550 -550
  43. package/src/monaco-language-service/index.js +2 -2
  44. package/src/output/comments.pyj +45 -45
  45. package/src/output/exceptions.pyj +201 -201
  46. package/src/output/jsx.pyj +164 -164
  47. package/src/output/loops.pyj +9 -0
  48. package/src/output/treeshake.pyj +182 -182
  49. package/src/output/utils.pyj +72 -72
  50. package/src/string_interpolation.pyj +72 -72
  51. package/src/tokenizer.pyj +1 -1
  52. package/src/unicode_aliases.pyj +576 -576
  53. package/src/utils.pyj +192 -192
  54. package/test/_import_one.pyj +37 -37
  55. package/test/_import_two/__init__.pyj +11 -11
  56. package/test/_import_two/level2/deep.pyj +4 -4
  57. package/test/_import_two/other.pyj +6 -6
  58. package/test/_import_two/sub.pyj +13 -13
  59. package/test/aes_vectors.pyj +421 -421
  60. package/test/annotations.pyj +80 -80
  61. package/test/contextlib.pyj +362 -0
  62. package/test/datetime.pyj +500 -0
  63. package/test/debugger_stmt.pyj +41 -0
  64. package/test/decorators.pyj +77 -77
  65. package/test/docstrings.pyj +39 -39
  66. package/test/elementmaker_test.pyj +45 -45
  67. package/test/functions.pyj +151 -151
  68. package/test/generators.pyj +41 -41
  69. package/test/generic.pyj +370 -370
  70. package/test/imports.pyj +72 -72
  71. package/test/internationalization.pyj +73 -73
  72. package/test/io.pyj +316 -0
  73. package/test/json.pyj +196 -0
  74. package/test/lint.pyj +164 -164
  75. package/test/loops.pyj +85 -85
  76. package/test/numpy.pyj +734 -734
  77. package/test/omit_function_metadata.pyj +20 -20
  78. package/test/repl.pyj +121 -121
  79. package/test/scoped_flags.pyj +76 -76
  80. package/test/unit/index.js +66 -0
  81. package/test/unit/language-service-dts.js +543 -543
  82. package/test/unit/language-service-hover.js +455 -455
  83. package/test/unit/language-service.js +1 -1
  84. package/test/unit/web-repl.js +533 -0
  85. package/tools/compiler.d.ts +367 -0
  86. package/tools/completer.js +131 -131
  87. package/tools/gettext.js +185 -185
  88. package/tools/ini.js +65 -65
  89. package/tools/msgfmt.js +187 -187
  90. package/tools/repl.js +223 -223
  91. package/tools/test.js +118 -118
  92. package/tools/utils.js +128 -128
  93. package/tools/web_repl.js +95 -95
  94. package/try +41 -41
  95. package/web-repl/env.js +196 -196
  96. package/web-repl/index.html +163 -163
  97. package/web-repl/prism.css +139 -139
  98. package/web-repl/prism.js +113 -113
  99. package/web-repl/rapydscript.js +224 -224
  100. package/web-repl/sha1.js +25 -25
@@ -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
+ }