rapydscript-ns 0.9.2 → 0.9.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.
Files changed (151) 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 +19 -0
  5. package/HACKING.md +103 -103
  6. package/LICENSE +24 -24
  7. package/PYTHON_GAPS.md +420 -0
  8. package/README.md +153 -29
  9. package/TODO.md +16 -118
  10. package/add-toc-to-readme +2 -2
  11. package/bin/export +75 -75
  12. package/bin/rapydscript +70 -70
  13. package/bin/web-repl-export +102 -102
  14. package/build +2 -2
  15. package/language-service/index.js +237 -8
  16. package/memory/project_string_impl.md +43 -0
  17. package/package.json +1 -1
  18. package/publish.py +37 -37
  19. package/release/baselib-plain-pretty.js +248 -38
  20. package/release/baselib-plain-ugly.js +8 -8
  21. package/release/compiler.js +778 -277
  22. package/release/signatures.json +30 -30
  23. package/session.vim +4 -4
  24. package/setup.cfg +2 -2
  25. package/src/ast.pyj +4 -1
  26. package/src/baselib-builtins.pyj +56 -2
  27. package/src/baselib-containers.pyj +2 -0
  28. package/src/baselib-errors.pyj +7 -3
  29. package/src/baselib-internal.pyj +51 -6
  30. package/src/baselib-str.pyj +5 -3
  31. package/src/compiler.pyj +36 -36
  32. package/src/errors.pyj +30 -30
  33. package/src/lib/aes.pyj +646 -646
  34. package/src/lib/asyncio.pyj +534 -0
  35. package/src/lib/base64.pyj +399 -0
  36. package/src/lib/bisect.pyj +73 -0
  37. package/src/lib/collections.pyj +1 -1
  38. package/src/lib/copy.pyj +120 -120
  39. package/src/lib/csv.pyj +494 -0
  40. package/src/lib/elementmaker.pyj +83 -83
  41. package/src/lib/encodings.pyj +126 -126
  42. package/src/lib/gettext.pyj +569 -569
  43. package/src/lib/heapq.pyj +98 -0
  44. package/src/lib/html.pyj +382 -0
  45. package/src/lib/http/__init__.pyj +98 -0
  46. package/src/lib/http/client.pyj +304 -0
  47. package/src/lib/http/cookies.pyj +236 -0
  48. package/src/lib/itertools.pyj +580 -580
  49. package/src/lib/logging.pyj +672 -0
  50. package/src/lib/math.pyj +193 -193
  51. package/src/lib/operator.pyj +11 -11
  52. package/src/lib/pythonize.pyj +20 -20
  53. package/src/lib/random.pyj +118 -118
  54. package/src/lib/react.pyj +74 -74
  55. package/src/lib/string.pyj +357 -0
  56. package/src/lib/textwrap.pyj +329 -0
  57. package/src/lib/traceback.pyj +63 -63
  58. package/src/lib/urllib/__init__.pyj +14 -0
  59. package/src/lib/urllib/error.pyj +66 -0
  60. package/src/lib/urllib/parse.pyj +475 -0
  61. package/src/lib/urllib/request.pyj +86 -0
  62. package/src/lib/uuid.pyj +77 -77
  63. package/src/monaco-language-service/analyzer.js +5 -2
  64. package/src/monaco-language-service/completions.js +26 -0
  65. package/src/monaco-language-service/diagnostics.js +202 -3
  66. package/src/monaco-language-service/dts.js +550 -550
  67. package/src/monaco-language-service/scope.js +1 -0
  68. package/src/output/comments.pyj +45 -45
  69. package/src/output/exceptions.pyj +201 -201
  70. package/src/output/functions.pyj +152 -6
  71. package/src/output/jsx.pyj +164 -164
  72. package/src/output/loops.pyj +17 -2
  73. package/src/output/modules.pyj +1 -1
  74. package/src/output/operators.pyj +15 -0
  75. package/src/output/stream.pyj +0 -1
  76. package/src/output/treeshake.pyj +182 -182
  77. package/src/output/utils.pyj +72 -72
  78. package/src/parse.pyj +80 -17
  79. package/src/string_interpolation.pyj +72 -72
  80. package/src/tokenizer.pyj +1 -1
  81. package/src/unicode_aliases.pyj +576 -576
  82. package/src/utils.pyj +192 -192
  83. package/test/_import_one.pyj +37 -37
  84. package/test/_import_two/__init__.pyj +11 -11
  85. package/test/_import_two/level2/deep.pyj +4 -4
  86. package/test/_import_two/other.pyj +6 -6
  87. package/test/_import_two/sub.pyj +13 -13
  88. package/test/aes_vectors.pyj +421 -421
  89. package/test/annotations.pyj +80 -80
  90. package/test/async_generators.pyj +144 -0
  91. package/test/asyncio.pyj +307 -0
  92. package/test/base64.pyj +202 -0
  93. package/test/bisect.pyj +178 -0
  94. package/test/csv.pyj +405 -0
  95. package/test/decorators.pyj +77 -77
  96. package/test/docstrings.pyj +39 -39
  97. package/test/elementmaker_test.pyj +45 -45
  98. package/test/float_special.pyj +64 -0
  99. package/test/functions.pyj +151 -151
  100. package/test/generators.pyj +41 -41
  101. package/test/generic.pyj +370 -370
  102. package/test/heapq.pyj +174 -0
  103. package/test/html.pyj +212 -0
  104. package/test/http.pyj +259 -0
  105. package/test/imports.pyj +79 -72
  106. package/test/internationalization.pyj +73 -73
  107. package/test/lint.pyj +164 -164
  108. package/test/logging.pyj +356 -0
  109. package/test/long.pyj +130 -0
  110. package/test/loops.pyj +85 -85
  111. package/test/numpy.pyj +734 -734
  112. package/test/parenthesized_with.pyj +141 -0
  113. package/test/python_compat.pyj +3 -5
  114. package/test/python_modulo.pyj +76 -0
  115. package/test/python_modulo_off.pyj +21 -0
  116. package/test/repl.pyj +121 -121
  117. package/test/scoped_flags.pyj +76 -76
  118. package/test/str.pyj +14 -0
  119. package/test/string.pyj +245 -0
  120. package/test/textwrap.pyj +172 -0
  121. package/test/type_display.pyj +48 -0
  122. package/test/type_enforcement.pyj +164 -0
  123. package/test/unit/index.js +14 -6
  124. package/test/unit/language-service-completions.js +119 -0
  125. package/test/unit/language-service-dts.js +543 -543
  126. package/test/unit/language-service-hover.js +455 -455
  127. package/test/unit/language-service-scope.js +32 -0
  128. package/test/unit/language-service.js +127 -3
  129. package/test/unit/run-language-service.js +17 -3
  130. package/test/unit/web-repl.js +2094 -29
  131. package/test/urllib.pyj +193 -0
  132. package/tools/compile.js +1 -1
  133. package/tools/compiler.d.ts +367 -367
  134. package/tools/completer.js +131 -131
  135. package/tools/embedded_compiler.js +7 -7
  136. package/tools/gettext.js +185 -185
  137. package/tools/ini.js +65 -65
  138. package/tools/msgfmt.js +187 -187
  139. package/tools/repl.js +223 -223
  140. package/tools/test.js +118 -118
  141. package/tools/utils.js +128 -128
  142. package/tools/web_repl.js +95 -95
  143. package/try +41 -41
  144. package/web-repl/env.js +196 -196
  145. package/web-repl/index.html +163 -163
  146. package/web-repl/main.js +1 -1
  147. package/web-repl/prism.css +139 -139
  148. package/web-repl/prism.js +113 -113
  149. package/web-repl/rapydscript.js +224 -224
  150. package/web-repl/sha1.js +25 -25
  151. package/test/omit_function_metadata.pyj +0 -20
@@ -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
+ }