tjs-lang 0.8.7 → 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 (75) hide show
  1. package/CLAUDE.md +12 -4
  2. package/dist/experiments/ambient/probe.d.ts +52 -0
  3. package/dist/index.js +115 -171
  4. package/dist/index.js.map +4 -4
  5. package/dist/scripts/build-editors.d.ts +19 -0
  6. package/dist/src/css/dimensions.d.ts +23 -0
  7. package/dist/src/css/index.d.ts +85 -0
  8. package/dist/src/css/predicates.d.ts +38 -0
  9. package/dist/src/css/shorthands.d.ts +31 -0
  10. package/dist/src/css/style.d.ts +48 -0
  11. package/dist/src/lang/emitters/js.d.ts +9 -1
  12. package/dist/src/lang/index.d.ts +2 -2
  13. package/dist/src/lang/parser-transforms.d.ts +9 -5
  14. package/dist/src/lang/parser.d.ts +2 -0
  15. package/dist/src/lang/predicate.d.ts +60 -0
  16. package/dist/src/lang/transpiler.d.ts +3 -2
  17. package/dist/src/lang/types.d.ts +16 -0
  18. package/dist/src/schema/index.d.ts +12 -0
  19. package/dist/tjs-browser-from-ts.js +20 -20
  20. package/dist/tjs-browser-from-ts.js.map +3 -3
  21. package/dist/tjs-browser.js +100 -90
  22. package/dist/tjs-browser.js.map +4 -4
  23. package/dist/tjs-css.js +193 -0
  24. package/dist/tjs-css.js.map +7 -0
  25. package/dist/tjs-eval.js +43 -42
  26. package/dist/tjs-eval.js.map +4 -4
  27. package/dist/tjs-from-ts.js +13 -13
  28. package/dist/tjs-from-ts.js.map +2 -2
  29. package/dist/tjs-lang.js +102 -92
  30. package/dist/tjs-lang.js.map +4 -4
  31. package/dist/tjs-runtime.js +10 -0
  32. package/dist/tjs-runtime.js.map +7 -0
  33. package/dist/tjs-schema.js +6 -0
  34. package/dist/tjs-schema.js.map +7 -0
  35. package/dist/tjs-vm.js +55 -54
  36. package/dist/tjs-vm.js.map +4 -4
  37. package/docs/ambient-contracts.md +261 -0
  38. package/editors/ace/ajs-mode.js +214 -233
  39. package/editors/codemirror/ajs-language.js +1570 -233
  40. package/editors/editors-build.test.ts +29 -0
  41. package/editors/monaco/ajs-monarch.js +239 -195
  42. package/llms.txt +4 -0
  43. package/package.json +35 -4
  44. package/src/css/css.test.ts +122 -0
  45. package/src/css/dimensions.test.ts +112 -0
  46. package/src/css/dimensions.ts +146 -0
  47. package/src/css/index.ts +243 -0
  48. package/src/css/perf.bench.test.ts +109 -0
  49. package/src/css/predicates.ts +232 -0
  50. package/src/css/property-aware.test.ts +84 -0
  51. package/src/css/shorthands.test.ts +90 -0
  52. package/src/css/shorthands.ts +113 -0
  53. package/src/css/style.test.ts +125 -0
  54. package/src/css/style.ts +134 -0
  55. package/src/index-tsfree.test.ts +58 -0
  56. package/src/lang/emit-verified-predicate.test.ts +95 -0
  57. package/src/lang/emitters/dts.test.ts +31 -0
  58. package/src/lang/emitters/dts.ts +23 -4
  59. package/src/lang/emitters/js.ts +33 -1
  60. package/src/lang/from-ts.test.ts +1 -1
  61. package/src/lang/generic-verified-predicate.test.ts +39 -0
  62. package/src/lang/index.ts +14 -5
  63. package/src/lang/parser-transforms.ts +109 -14
  64. package/src/lang/parser.ts +9 -2
  65. package/src/lang/predicate-evaluator.test.ts +49 -0
  66. package/src/lang/predicate-report.test.ts +54 -0
  67. package/src/lang/predicate.ts +268 -0
  68. package/src/lang/redos-lint.test.ts +81 -0
  69. package/src/lang/transpiler.ts +13 -0
  70. package/src/lang/type-verified-predicate.test.ts +83 -0
  71. package/src/lang/types.ts +17 -0
  72. package/src/lang/typescript-syntax.test.ts +2 -1
  73. package/src/schema/index.ts +62 -0
  74. package/src/schema/schema.test.ts +66 -0
  75. package/src/use-cases/bootstrap.test.ts +14 -4
@@ -1,287 +1,1624 @@
1
- /**
2
- * CodeMirror 6 Language Support for AsyncJS (ES Module)
3
- *
4
- * Usage with CDN:
5
- * ```html
6
- * <script type="module">
7
- * import { EditorView, basicSetup } from 'https://esm.sh/codemirror'
8
- * import { EditorState } from 'https://esm.sh/@codemirror/state'
9
- * import { javascript } from 'https://esm.sh/@codemirror/lang-javascript'
10
- *
11
- * // Inline the forbidden keyword highlighter
12
- * const FORBIDDEN = new Set(['new', 'class', 'async', 'await', 'var', 'this', 'super', 'extends', 'implements', 'interface', 'type', 'yield', 'import', 'export', 'require'])
13
- *
14
- * // ... see full example below
15
- * </script>
16
- * ```
17
- *
18
- * For a complete working example, see the ajsExtension function below.
19
- */
1
+ // editors/codemirror/ajs-language.ts
2
+ import { javascript } from "@codemirror/lang-javascript";
3
+ import {
4
+ HighlightStyle,
5
+ LanguageSupport
6
+ } from "@codemirror/language";
7
+ import {
8
+ EditorView,
9
+ Decoration,
10
+ ViewPlugin
11
+ } from "@codemirror/view";
12
+ import { RangeSetBuilder } from "@codemirror/state";
13
+ import {
14
+ autocompletion,
15
+ snippetCompletion
16
+ } from "@codemirror/autocomplete";
20
17
 
21
- import { EditorView, Decoration, ViewPlugin } from '@codemirror/view'
22
- import { RangeSetBuilder } from '@codemirror/state'
23
- import { javascript } from '@codemirror/lang-javascript'
18
+ // editors/ajs-syntax.ts
19
+ var KEYWORDS = [
20
+ "function",
21
+ "return",
22
+ "if",
23
+ "else",
24
+ "while",
25
+ "for",
26
+ "of",
27
+ "in",
28
+ "try",
29
+ "catch",
30
+ "finally",
31
+ "let",
32
+ "const",
33
+ "true",
34
+ "false",
35
+ "null",
36
+ "undefined"
37
+ ];
38
+ var FORBIDDEN_KEYWORDS = [
39
+ // Object-oriented constructs
40
+ "new",
41
+ "class",
42
+ "extends",
43
+ "super",
44
+ "this",
45
+ "implements",
46
+ "interface",
47
+ "abstract",
48
+ "static",
49
+ "private",
50
+ "protected",
51
+ "public",
52
+ // Async constructs (not needed - runtime handles async)
53
+ "async",
54
+ "await",
55
+ "yield",
56
+ // Module system (not supported)
57
+ "import",
58
+ "export",
59
+ "require",
60
+ "module",
61
+ // Other unsupported
62
+ "var",
63
+ // use let/const
64
+ "throw",
65
+ // use Error() for monadic error flow
66
+ "switch",
67
+ // use if/else chains
68
+ "case",
69
+ "default",
70
+ // (as switch keyword)
71
+ "with",
72
+ "delete",
73
+ "void",
74
+ "typeof",
75
+ // use type-by-example instead
76
+ "instanceof",
77
+ "debugger",
78
+ "eval",
79
+ // TypeScript-specific (not supported)
80
+ "type",
81
+ "enum",
82
+ "namespace",
83
+ "declare",
84
+ "readonly",
85
+ "as",
86
+ "is",
87
+ "keyof",
88
+ "infer",
89
+ "never",
90
+ "unknown"
91
+ ];
92
+ var TYPE_CONSTRUCTORS = [
93
+ "Date",
94
+ "Set",
95
+ "Map",
96
+ "Array",
97
+ "Object",
98
+ "String",
99
+ "Number",
100
+ "Boolean",
101
+ "RegExp",
102
+ "Error",
103
+ "JSON",
104
+ "Math",
105
+ "Schema"
106
+ // AsyncJS-specific
107
+ ];
108
+ var OPERATORS = [
109
+ // Assignment
110
+ "=",
111
+ "+=",
112
+ "-=",
113
+ "*=",
114
+ "/=",
115
+ "%=",
116
+ // Comparison
117
+ "==",
118
+ "===",
119
+ "!=",
120
+ "!==",
121
+ "<",
122
+ ">",
123
+ "<=",
124
+ ">=",
125
+ // Arithmetic
126
+ "+",
127
+ "-",
128
+ "*",
129
+ "/",
130
+ "%",
131
+ "**",
132
+ // Logical
133
+ "&&",
134
+ "||",
135
+ "??",
136
+ "!",
137
+ // Bitwise (limited support)
138
+ "&",
139
+ "|",
140
+ "^",
141
+ "~",
142
+ "<<",
143
+ ">>",
144
+ ">>>",
145
+ // Other
146
+ "?",
147
+ ":",
148
+ ".",
149
+ "?.",
150
+ "?.(",
151
+ "?.[",
152
+ "..."
153
+ ];
154
+ var FORBIDDEN_SET = new Set(FORBIDDEN_KEYWORDS);
155
+ var KEYWORDS_SET = new Set(KEYWORDS);
156
+ var FORBIDDEN_PATTERN = new RegExp(
157
+ `\\b(${FORBIDDEN_KEYWORDS.join("|")})\\b`,
158
+ "g"
159
+ );
24
160
 
25
- /**
26
- * Forbidden keywords in AsyncJS
27
- */
28
- export const FORBIDDEN_KEYWORDS = new Set([
29
- 'new',
30
- 'class',
31
- 'async',
32
- 'await',
33
- 'var',
34
- 'this',
35
- 'super',
36
- 'extends',
37
- 'implements',
38
- 'interface',
39
- 'type',
40
- 'yield',
41
- 'import',
42
- 'export',
43
- 'require',
44
- ])
161
+ // editors/tjs-syntax.ts
162
+ var TJS_KEYWORDS = [
163
+ "test",
164
+ // inline tests
165
+ "mock",
166
+ // test setup blocks
167
+ "unsafe",
168
+ // exception-catching blocks
169
+ "async",
170
+ // TJS allows async (unlike sandboxed AJS)
171
+ "await",
172
+ "throw",
173
+ "import",
174
+ "export",
175
+ // Class support
176
+ "class",
177
+ "extends",
178
+ "super",
179
+ "this",
180
+ "new",
181
+ "static",
182
+ // JS operators
183
+ "typeof",
184
+ "instanceof",
185
+ "delete"
186
+ ];
187
+ var KEYWORDS2 = [...KEYWORDS, ...TJS_KEYWORDS];
188
+ var FORBIDDEN_KEYWORDS2 = FORBIDDEN_KEYWORDS.filter(
189
+ (k) => ![
190
+ "async",
191
+ "await",
192
+ "throw",
193
+ "import",
194
+ "export",
195
+ // Class support (TjsClass mode)
196
+ "class",
197
+ "extends",
198
+ "super",
199
+ "this",
200
+ "new",
201
+ "static",
202
+ // Valid JS operators
203
+ "typeof",
204
+ "instanceof",
205
+ "delete"
206
+ ].includes(k)
207
+ );
208
+ var TYPE_CONSTRUCTORS2 = [
209
+ ...TYPE_CONSTRUCTORS,
210
+ "expect",
211
+ // test assertions
212
+ "assert"
213
+ // simple assertions
214
+ ];
215
+ var OPERATORS2 = [...OPERATORS, "->"];
45
216
 
46
- /**
47
- * Create the AsyncJS extension for CodeMirror 6
48
- *
49
- * This function creates the extension inline since CodeMirror 6's
50
- * modular architecture makes bundling complex.
51
- *
52
- * @param {Object} cm - Object containing CodeMirror imports
53
- * @param {typeof import('@codemirror/view')} cm.view
54
- * @param {typeof import('@codemirror/state')} cm.state
55
- * @param {typeof import('@codemirror/lang-javascript')} cm.langJs
56
- * @returns {import('@codemirror/state').Extension}
57
- */
58
- export function createAjsExtension({ view, state, langJs }) {
59
- const { EditorView, Decoration, ViewPlugin } = view
60
- const { RangeSetBuilder } = state
61
- const { javascript } = langJs
62
-
63
- const forbiddenMark = Decoration.mark({ class: 'cm-ajs-forbidden' })
64
-
65
- const forbiddenHighlighter = ViewPlugin.fromClass(
66
- class {
67
- constructor(view) {
68
- this.decorations = this.buildDecorations(view)
217
+ // editors/scope-symbols.ts
218
+ import * as acorn from "acorn";
219
+ import * as acornLoose from "acorn-loose";
220
+ import * as walk from "acorn-walk";
221
+ function parse3(source) {
222
+ try {
223
+ return acorn.parse(source, { ecmaVersion: "latest" });
224
+ } catch {
225
+ try {
226
+ return acornLoose.parse(source, { ecmaVersion: "latest" });
227
+ } catch {
228
+ return null;
229
+ }
230
+ }
231
+ }
232
+ function collectPattern(pat, onName, member) {
233
+ if (!pat) return;
234
+ switch (pat.type) {
235
+ case "Identifier":
236
+ onName(pat.name, member);
237
+ return;
238
+ case "ObjectPattern":
239
+ for (const p of pat.properties) {
240
+ if (p.type === "RestElement") {
241
+ collectPattern(p.argument, onName);
242
+ } else {
243
+ const key = p.key && (p.key.name ?? p.key.value);
244
+ collectPattern(
245
+ p.value,
246
+ onName,
247
+ typeof key === "string" ? key : void 0
248
+ );
249
+ }
69
250
  }
70
-
71
- update(update) {
72
- if (update.docChanged || update.viewportChanged) {
73
- this.decorations = this.buildDecorations(update.view)
251
+ return;
252
+ case "ArrayPattern":
253
+ for (const el of pat.elements) collectPattern(el, onName);
254
+ return;
255
+ case "AssignmentPattern":
256
+ collectPattern(pat.left, onName, member);
257
+ return;
258
+ case "RestElement":
259
+ collectPattern(pat.argument, onName);
260
+ return;
261
+ }
262
+ }
263
+ var inRange = (node, position) => typeof node.start === "number" && typeof node.end === "number" && node.start <= position && position <= node.end;
264
+ function collectScopeSymbols(source, position = source.length) {
265
+ const ast = parse3(source);
266
+ if (!ast) return [];
267
+ const byName = /* @__PURE__ */ new Map();
268
+ const add = (s) => byName.set(s.name, s);
269
+ walk.full(ast, (node) => {
270
+ switch (node.type) {
271
+ case "VariableDeclaration": {
272
+ if (node.start >= position) return;
273
+ for (const decl of node.declarations) {
274
+ const initText = decl.init && typeof decl.init.start === "number" ? source.slice(decl.init.start, decl.init.end) : void 0;
275
+ collectPattern(
276
+ decl.id,
277
+ (name, member) => add({
278
+ name,
279
+ kind: "variable",
280
+ origin: {
281
+ via: member != null ? "destructure" : "init",
282
+ expr: initText,
283
+ member
284
+ }
285
+ })
286
+ );
74
287
  }
288
+ return;
75
289
  }
76
-
77
- buildDecorations(view) {
78
- const builder = new RangeSetBuilder()
79
- const doc = view.state.doc.toString()
80
- const pattern = new RegExp(
81
- `\\b(${Array.from(FORBIDDEN_KEYWORDS).join('|')})\\b`,
82
- 'g'
83
- )
84
-
85
- let match
86
- while ((match = pattern.exec(doc)) !== null) {
87
- builder.add(match.index, match.index + match[0].length, forbiddenMark)
290
+ case "FunctionDeclaration": {
291
+ if (node.id && node.start < position)
292
+ add({
293
+ name: node.id.name,
294
+ kind: "function",
295
+ origin: { via: "function" }
296
+ });
297
+ if (inRange(node, position))
298
+ for (const p of node.params)
299
+ collectPattern(
300
+ p,
301
+ (name) => add({ name, kind: "parameter", origin: { via: "param" } })
302
+ );
303
+ return;
304
+ }
305
+ case "FunctionExpression":
306
+ case "ArrowFunctionExpression": {
307
+ if (inRange(node, position))
308
+ for (const p of node.params)
309
+ collectPattern(
310
+ p,
311
+ (name) => add({ name, kind: "parameter", origin: { via: "param" } })
312
+ );
313
+ return;
314
+ }
315
+ case "ImportDeclaration": {
316
+ const module = String(node.source?.value ?? "");
317
+ for (const spec of node.specifiers) {
318
+ add({
319
+ name: spec.local.name,
320
+ kind: "import",
321
+ origin: { via: "import", module }
322
+ });
88
323
  }
89
-
90
- return builder.finish()
324
+ return;
91
325
  }
92
- },
93
- { decorations: (v) => v.decorations }
94
- )
95
-
96
- const ajsTheme = EditorView.theme({
97
- '.cm-ajs-forbidden': {
98
- color: '#dc2626',
99
- textDecoration: 'wavy underline #dc2626',
100
- backgroundColor: 'rgba(220, 38, 38, 0.1)',
101
- },
102
- })
103
-
104
- return [javascript(), forbiddenHighlighter, ajsTheme]
105
- }
106
-
107
- // Alias for backwards compatibility
108
- export { ajsEditorExtension as ajs }
109
-
110
- /**
111
- * Minimal CSS for AsyncJS highlighting (can be added to page if theme doesn't work)
112
- */
113
- export const ajsStyles = `
114
- .cm-ajs-forbidden {
115
- color: #dc2626 !important;
116
- text-decoration: wavy underline #dc2626;
117
- background-color: rgba(220, 38, 38, 0.1);
326
+ }
327
+ });
328
+ return [...byName.values()];
118
329
  }
119
- `
120
330
 
121
- /**
122
- * Find all string and comment regions in the document
123
- * Returns array of [start, end] ranges to skip
124
- */
331
+ // editors/codemirror/ajs-language.ts
332
+ var FORBIDDEN_KEYWORDS3 = new Set(FORBIDDEN_KEYWORDS);
333
+ var TJS_FORBIDDEN_KEYWORDS = new Set(FORBIDDEN_KEYWORDS2);
334
+ var forbiddenMark = Decoration.mark({
335
+ class: "cm-ajs-forbidden"
336
+ });
337
+ var tjsSpecialMark = Decoration.mark({
338
+ class: "cm-tjs-special"
339
+ });
125
340
  function findSkipRegions(doc) {
126
- const regions = []
127
- const len = doc.length
128
- let i = 0
129
-
341
+ const regions = [];
342
+ const len = doc.length;
343
+ let i = 0;
130
344
  while (i < len) {
131
- const ch = doc[i]
132
- const next = doc[i + 1]
133
-
134
- // Single-line comment
135
- if (ch === '/' && next === '/') {
136
- const start = i
137
- i += 2
138
- while (i < len && doc[i] !== '\n') i++
139
- regions.push([start, i])
140
- continue
345
+ const ch = doc[i];
346
+ const next = doc[i + 1];
347
+ if (ch === "/" && next === "/") {
348
+ const start = i;
349
+ i += 2;
350
+ while (i < len && doc[i] !== "\n") i++;
351
+ regions.push([start, i]);
352
+ continue;
141
353
  }
142
-
143
- // Multi-line comment
144
- if (ch === '/' && next === '*') {
145
- const start = i
146
- i += 2
147
- while (i < len - 1 && !(doc[i] === '*' && doc[i + 1] === '/')) i++
148
- i += 2 // skip */
149
- regions.push([start, i])
150
- continue
354
+ if (ch === "/" && next === "*") {
355
+ const start = i;
356
+ i += 2;
357
+ while (i < len - 1 && !(doc[i] === "*" && doc[i + 1] === "/")) i++;
358
+ i += 2;
359
+ regions.push([start, i]);
360
+ continue;
151
361
  }
152
-
153
- // Template literal - skip string parts but NOT ${...} expressions
154
- if (ch === '`') {
155
- let stringStart = i
156
- i++
362
+ if (ch === "`") {
363
+ let stringStart = i;
364
+ i++;
157
365
  while (i < len) {
158
- if (doc[i] === '\\') {
159
- i += 2 // skip escaped char
160
- continue
366
+ if (doc[i] === "\\") {
367
+ i += 2;
368
+ continue;
161
369
  }
162
- if (doc[i] === '`') {
163
- // End of template - add final string region
164
- regions.push([stringStart, i + 1])
165
- i++
166
- break
370
+ if (doc[i] === "`") {
371
+ regions.push([stringStart, i + 1]);
372
+ i++;
373
+ break;
167
374
  }
168
- if (doc[i] === '$' && doc[i + 1] === '{') {
169
- // Add string region before ${
170
- regions.push([stringStart, i])
171
- i += 2 // skip ${
172
- // Skip the expression inside ${...} (don't add to regions - it's code!)
173
- let braceDepth = 1
375
+ if (doc[i] === "$" && doc[i + 1] === "{") {
376
+ regions.push([stringStart, i]);
377
+ i += 2;
378
+ let braceDepth = 1;
174
379
  while (i < len && braceDepth > 0) {
175
- if (doc[i] === '{') braceDepth++
176
- else if (doc[i] === '}') braceDepth--
177
- if (braceDepth > 0) i++
380
+ if (doc[i] === "{") braceDepth++;
381
+ else if (doc[i] === "}") braceDepth--;
382
+ if (braceDepth > 0) i++;
178
383
  }
179
- i++ // skip closing }
180
- stringStart = i // next string region starts here
181
- continue
384
+ i++;
385
+ stringStart = i;
386
+ continue;
182
387
  }
183
- i++
388
+ i++;
184
389
  }
185
- continue
390
+ continue;
186
391
  }
187
-
188
- // Single or double quoted string
189
392
  if (ch === '"' || ch === "'") {
190
- const quote = ch
191
- const start = i
192
- i++
393
+ const quote = ch;
394
+ const start = i;
395
+ i++;
193
396
  while (i < len) {
194
- if (doc[i] === '\\') {
195
- i += 2 // skip escaped char
196
- continue
397
+ if (doc[i] === "\\") {
398
+ i += 2;
399
+ continue;
197
400
  }
198
401
  if (doc[i] === quote) {
199
- i++
200
- break
402
+ i++;
403
+ break;
201
404
  }
202
- if (doc[i] === '\n') break // unterminated string
203
- i++
405
+ if (doc[i] === "\n") break;
406
+ i++;
204
407
  }
205
- regions.push([start, i])
206
- continue
408
+ regions.push([start, i]);
409
+ continue;
207
410
  }
208
-
209
- i++
411
+ i++;
210
412
  }
211
-
212
- return regions
413
+ return regions;
213
414
  }
214
-
215
- /**
216
- * Check if a position is inside any skip region
217
- */
218
415
  function isInSkipRegion(pos, regions) {
219
416
  for (const [start, end] of regions) {
220
- if (pos >= start && pos < end) return true
221
- if (start > pos) break // regions are sorted, no need to check further
417
+ if (pos >= start && pos < end) return true;
418
+ if (start > pos) break;
222
419
  }
223
- return false
420
+ return false;
224
421
  }
225
-
226
- /**
227
- * Ready-to-use AsyncJS language extension for CodeMirror 6
228
- *
229
- * Returns an array of extensions that provide:
230
- * - JavaScript syntax highlighting
231
- * - Red underline highlighting for forbidden keywords (new, class, async, etc.)
232
- * but NOT inside strings or comments
233
- *
234
- * @returns {import('@codemirror/state').Extension[]}
235
- */
236
- export function ajsEditorExtension() {
237
- const forbiddenMark = Decoration.mark({ class: 'cm-ajs-forbidden' })
238
-
239
- const forbiddenHighlighter = ViewPlugin.fromClass(
422
+ function createForbiddenHighlighter(forbiddenSet) {
423
+ const pattern = new RegExp(`\\b(${[...forbiddenSet].join("|")})\\b`, "g");
424
+ return ViewPlugin.fromClass(
240
425
  class {
426
+ decorations;
241
427
  constructor(view) {
242
- this.decorations = this.buildDecorations(view)
428
+ this.decorations = this.buildDecorations(view);
243
429
  }
244
-
245
430
  update(update) {
246
431
  if (update.docChanged || update.viewportChanged) {
247
- this.decorations = this.buildDecorations(update.view)
432
+ this.decorations = this.buildDecorations(update.view);
248
433
  }
249
434
  }
250
-
251
435
  buildDecorations(view) {
252
- const builder = new RangeSetBuilder()
253
- const doc = view.state.doc.toString()
254
- const skipRegions = findSkipRegions(doc)
255
- const pattern = new RegExp(
256
- `\\b(${Array.from(FORBIDDEN_KEYWORDS).join('|')})\\b`,
257
- 'g'
258
- )
259
-
260
- let match
261
- while ((match = pattern.exec(doc)) !== null) {
262
- // Skip if inside string or comment
436
+ const builder = new RangeSetBuilder();
437
+ const doc = view.state.doc.toString();
438
+ const skipRegions = findSkipRegions(doc);
439
+ const regex = new RegExp(pattern.source, "g");
440
+ let match;
441
+ while ((match = regex.exec(doc)) !== null) {
263
442
  if (!isInSkipRegion(match.index, skipRegions)) {
264
443
  builder.add(
265
444
  match.index,
266
445
  match.index + match[0].length,
267
446
  forbiddenMark
268
- )
447
+ );
269
448
  }
270
449
  }
271
-
272
- return builder.finish()
450
+ return builder.finish();
273
451
  }
274
452
  },
275
- { decorations: (v) => v.decorations }
276
- )
277
-
278
- const ajsTheme = EditorView.theme({
279
- '.cm-ajs-forbidden': {
280
- color: '#dc2626',
281
- textDecoration: 'wavy underline #dc2626',
282
- backgroundColor: 'rgba(220, 38, 38, 0.1)',
283
- },
453
+ {
454
+ decorations: (v) => v.decorations
455
+ }
456
+ );
457
+ }
458
+ var forbiddenHighlighter = createForbiddenHighlighter(FORBIDDEN_KEYWORDS3);
459
+ var tjsForbiddenHighlighter = createForbiddenHighlighter(
460
+ TJS_FORBIDDEN_KEYWORDS
461
+ );
462
+ var tryWithoutCatchHighlighter = ViewPlugin.fromClass(
463
+ class {
464
+ decorations;
465
+ constructor(view) {
466
+ this.decorations = this.buildDecorations(view);
467
+ }
468
+ update(update) {
469
+ if (update.docChanged || update.viewportChanged) {
470
+ this.decorations = this.buildDecorations(update.view);
471
+ }
472
+ }
473
+ buildDecorations(view) {
474
+ const builder = new RangeSetBuilder();
475
+ const doc = view.state.doc.toString();
476
+ const skipRegions = findSkipRegions(doc);
477
+ const tryPattern = /\btry\s*\{/g;
478
+ let match;
479
+ while ((match = tryPattern.exec(doc)) !== null) {
480
+ if (isInSkipRegion(match.index, skipRegions)) continue;
481
+ const braceStart = match.index + match[0].length - 1;
482
+ let depth = 1;
483
+ let j = braceStart + 1;
484
+ while (j < doc.length && depth > 0) {
485
+ const char = doc[j];
486
+ if (char === "{") depth++;
487
+ else if (char === "}") depth--;
488
+ j++;
489
+ }
490
+ if (depth !== 0) continue;
491
+ const afterTry = doc.slice(j).match(/^\s*(catch|finally)\b/);
492
+ if (!afterTry) {
493
+ const tryKeywordEnd = match.index + 3;
494
+ builder.add(match.index, tryKeywordEnd, tjsSpecialMark);
495
+ }
496
+ }
497
+ return builder.finish();
498
+ }
499
+ },
500
+ {
501
+ decorations: (v) => v.decorations
502
+ }
503
+ );
504
+ var ajsTheme = EditorView.theme({
505
+ ".cm-ajs-forbidden": {
506
+ color: "#dc2626",
507
+ textDecoration: "wavy underline #dc2626",
508
+ backgroundColor: "rgba(220, 38, 38, 0.1)"
509
+ },
510
+ ".cm-tjs-special": {
511
+ color: "#7c3aed",
512
+ fontWeight: "bold",
513
+ backgroundColor: "rgba(124, 58, 237, 0.1)"
514
+ }
515
+ });
516
+ var ajsHighlightStyle = HighlightStyle.define([
517
+ // Standard highlighting is inherited from JavaScript
518
+ // Add any AsyncJS-specific overrides here
519
+ ]);
520
+ function memberToCompletion(m) {
521
+ if (m.type === "method") {
522
+ const hasArgs = m.detail !== "()";
523
+ return snippetCompletion(`${m.label}(${hasArgs ? "${1}" : ""})`, {
524
+ label: m.label,
525
+ type: "method",
526
+ detail: m.detail
527
+ });
528
+ }
529
+ return { label: m.label, type: "property", detail: m.detail };
530
+ }
531
+ var TJS_COMPLETIONS = [
532
+ { label: "function", type: "keyword", detail: "Declare a function" },
533
+ { label: "const", type: "keyword", detail: "Declare a constant" },
534
+ { label: "let", type: "keyword", detail: "Declare a variable" },
535
+ { label: "if", type: "keyword", detail: "Conditional statement" },
536
+ { label: "else", type: "keyword", detail: "Else branch" },
537
+ { label: "while", type: "keyword", detail: "While loop" },
538
+ { label: "for", type: "keyword", detail: "For loop" },
539
+ { label: "return", type: "keyword", detail: "Return from function" },
540
+ { label: "try", type: "keyword", detail: "Try block" },
541
+ { label: "catch", type: "keyword", detail: "Catch block" },
542
+ { label: "import", type: "keyword", detail: "Import module" },
543
+ { label: "export", type: "keyword", detail: "Export declaration" },
544
+ snippetCompletion("test('${description}') {\n ${}\n}", {
545
+ label: "test",
546
+ type: "keyword",
547
+ detail: "Inline test block"
548
+ }),
549
+ snippetCompletion("mock {\n ${}\n}", {
550
+ label: "mock",
551
+ type: "keyword",
552
+ detail: "Mock setup block"
553
+ }),
554
+ snippetCompletion("unsafe {\n ${}\n}", {
555
+ label: "unsafe",
556
+ type: "keyword",
557
+ detail: "Skip type validation"
284
558
  })
285
-
286
- return [javascript(), forbiddenHighlighter, ajsTheme]
559
+ ];
560
+ var TJS_TYPES = [
561
+ { label: "''", type: "type", detail: "String type" },
562
+ { label: "0", type: "type", detail: "Number type" },
563
+ { label: "true", type: "type", detail: "Boolean type" },
564
+ { label: "null", type: "type", detail: "Null type" },
565
+ { label: "undefined", type: "type", detail: "Undefined type" },
566
+ { label: "['']", type: "type", detail: "Array of strings" },
567
+ { label: "[0]", type: "type", detail: "Array of numbers" },
568
+ { label: "{}", type: "type", detail: "Object type" },
569
+ { label: "any", type: "type", detail: "Any type" }
570
+ ];
571
+ var RUNTIME_COMPLETIONS = [
572
+ snippetCompletion("isError(${value})", {
573
+ label: "isError",
574
+ type: "function",
575
+ detail: "(value: any) -> boolean",
576
+ info: "Check if a value is a TJS error"
577
+ }),
578
+ snippetCompletion("error('${message}')", {
579
+ label: "error",
580
+ type: "function",
581
+ detail: "(message: string) -> TJSError",
582
+ info: "Create a TJS error object"
583
+ }),
584
+ snippetCompletion("typeOf(${value})", {
585
+ label: "typeOf",
586
+ type: "function",
587
+ detail: "(value: any) -> string",
588
+ info: "Get type name (fixed typeof)"
589
+ }),
590
+ snippetCompletion("expect(${actual})", {
591
+ label: "expect",
592
+ type: "function",
593
+ detail: "(actual: any) -> Matchers",
594
+ info: "Test assertion"
595
+ })
596
+ ];
597
+ var GLOBAL_COMPLETIONS = [
598
+ // Console
599
+ {
600
+ label: "console",
601
+ type: "variable",
602
+ detail: "Console object",
603
+ info: "Logging and debugging"
604
+ },
605
+ // Math
606
+ {
607
+ label: "Math",
608
+ type: "variable",
609
+ detail: "Math object",
610
+ info: "Mathematical functions and constants"
611
+ },
612
+ // JSON
613
+ {
614
+ label: "JSON",
615
+ type: "variable",
616
+ detail: "JSON object",
617
+ info: "JSON parse and stringify"
618
+ },
619
+ // Constructors / types
620
+ {
621
+ label: "Array",
622
+ type: "class",
623
+ detail: "Array constructor",
624
+ info: "Create arrays"
625
+ },
626
+ {
627
+ label: "Object",
628
+ type: "class",
629
+ detail: "Object constructor",
630
+ info: "Object utilities"
631
+ },
632
+ {
633
+ label: "String",
634
+ type: "class",
635
+ detail: "String constructor",
636
+ info: "String utilities"
637
+ },
638
+ {
639
+ label: "Number",
640
+ type: "class",
641
+ detail: "Number constructor",
642
+ info: "Number utilities"
643
+ },
644
+ { label: "Boolean", type: "class", detail: "Boolean constructor" },
645
+ {
646
+ label: "Date",
647
+ type: "class",
648
+ detail: "Date constructor",
649
+ info: "Date and time"
650
+ },
651
+ {
652
+ label: "RegExp",
653
+ type: "class",
654
+ detail: "RegExp constructor",
655
+ info: "Regular expressions"
656
+ },
657
+ {
658
+ label: "Map",
659
+ type: "class",
660
+ detail: "Map constructor",
661
+ info: "Key-value collection"
662
+ },
663
+ {
664
+ label: "Set",
665
+ type: "class",
666
+ detail: "Set constructor",
667
+ info: "Unique value collection"
668
+ },
669
+ { label: "WeakMap", type: "class", detail: "WeakMap constructor" },
670
+ { label: "WeakSet", type: "class", detail: "WeakSet constructor" },
671
+ { label: "Symbol", type: "class", detail: "Symbol constructor" },
672
+ { label: "BigInt", type: "class", detail: "BigInt constructor" },
673
+ // Error types
674
+ { label: "Error", type: "class", detail: "Error constructor" },
675
+ { label: "TypeError", type: "class", detail: "TypeError constructor" },
676
+ { label: "RangeError", type: "class", detail: "RangeError constructor" },
677
+ { label: "SyntaxError", type: "class", detail: "SyntaxError constructor" },
678
+ {
679
+ label: "ReferenceError",
680
+ type: "class",
681
+ detail: "ReferenceError constructor"
682
+ },
683
+ // Typed arrays
684
+ { label: "ArrayBuffer", type: "class", detail: "ArrayBuffer constructor" },
685
+ { label: "Uint8Array", type: "class", detail: "Uint8Array constructor" },
686
+ { label: "Int8Array", type: "class", detail: "Int8Array constructor" },
687
+ { label: "Uint16Array", type: "class", detail: "Uint16Array constructor" },
688
+ { label: "Int16Array", type: "class", detail: "Int16Array constructor" },
689
+ { label: "Uint32Array", type: "class", detail: "Uint32Array constructor" },
690
+ { label: "Int32Array", type: "class", detail: "Int32Array constructor" },
691
+ { label: "Float32Array", type: "class", detail: "Float32Array constructor" },
692
+ { label: "Float64Array", type: "class", detail: "Float64Array constructor" },
693
+ // Promises (though async/await is forbidden, Promise itself may be useful)
694
+ { label: "Promise", type: "class", detail: "Promise constructor" },
695
+ // Global functions
696
+ { label: "parseInt", type: "function", detail: "(string, radix?) -> number" },
697
+ { label: "parseFloat", type: "function", detail: "(string) -> number" },
698
+ { label: "isNaN", type: "function", detail: "(value) -> boolean" },
699
+ { label: "isFinite", type: "function", detail: "(value) -> boolean" },
700
+ { label: "encodeURI", type: "function", detail: "(uri) -> string" },
701
+ { label: "decodeURI", type: "function", detail: "(encodedURI) -> string" },
702
+ {
703
+ label: "encodeURIComponent",
704
+ type: "function",
705
+ detail: "(component) -> string"
706
+ },
707
+ {
708
+ label: "decodeURIComponent",
709
+ type: "function",
710
+ detail: "(encoded) -> string"
711
+ },
712
+ // Global values
713
+ { label: "undefined", type: "keyword", detail: "Undefined value" },
714
+ { label: "null", type: "keyword", detail: "Null value" },
715
+ { label: "NaN", type: "keyword", detail: "Not a Number" },
716
+ { label: "Infinity", type: "keyword", detail: "Positive infinity" },
717
+ { label: "globalThis", type: "variable", detail: "Global object" }
718
+ ];
719
+ var EXPECT_MATCHERS = [
720
+ snippetCompletion("toBe(${expected})", {
721
+ label: "toBe",
722
+ type: "method",
723
+ detail: "(expected: any)",
724
+ info: "Strict equality (===)"
725
+ }),
726
+ snippetCompletion("toEqual(${expected})", {
727
+ label: "toEqual",
728
+ type: "method",
729
+ detail: "(expected: any)",
730
+ info: "Deep equality"
731
+ }),
732
+ snippetCompletion("toContain(${item})", {
733
+ label: "toContain",
734
+ type: "method",
735
+ detail: "(item: any)",
736
+ info: "Array/string contains"
737
+ }),
738
+ { label: "toThrow", type: "method", detail: "()", info: "Throws an error" },
739
+ { label: "toBeTruthy", type: "method", detail: "()", info: "Is truthy" },
740
+ { label: "toBeFalsy", type: "method", detail: "()", info: "Is falsy" },
741
+ { label: "toBeNull", type: "method", detail: "()", info: "Is null" },
742
+ {
743
+ label: "toBeUndefined",
744
+ type: "method",
745
+ detail: "()",
746
+ info: "Is undefined"
747
+ }
748
+ ];
749
+ function extractFunctions(source) {
750
+ const completions = [];
751
+ const funcRegex = /function\s+(\w+)\s*\(([^)]*)\)/g;
752
+ let match;
753
+ while ((match = funcRegex.exec(source)) !== null) {
754
+ const [, name, params] = match;
755
+ completions.push(
756
+ snippetCompletion(`${name}(${params ? "${1}" : ""})`, {
757
+ label: name,
758
+ type: "function",
759
+ detail: `(${params})`
760
+ })
761
+ );
762
+ }
763
+ return completions;
764
+ }
765
+ function extractVariables(source, position) {
766
+ const completions = [];
767
+ const before = source.slice(0, position);
768
+ const varRegex = /(?:const|let)\s+(\w+)\s*=/g;
769
+ let match;
770
+ while ((match = varRegex.exec(before)) !== null) {
771
+ completions.push({
772
+ label: match[1],
773
+ type: "variable"
774
+ });
775
+ }
776
+ return completions;
777
+ }
778
+ function completionsFromScope(source, position) {
779
+ const symbols = collectScopeSymbols(source, position);
780
+ if (symbols.length === 0) {
781
+ return [...extractFunctions(source), ...extractVariables(source, position)];
782
+ }
783
+ return symbols.map((s) => {
784
+ const detail = s.origin?.member && s.origin.expr ? `\u2208 ${s.origin.expr}` : s.kind === "import" && s.origin?.module ? `import '${s.origin.module}'` : s.kind === "parameter" ? "parameter" : void 0;
785
+ if (s.kind === "function") {
786
+ return snippetCompletion(`${s.name}($1)`, {
787
+ label: s.name,
788
+ type: "function",
789
+ detail
790
+ });
791
+ }
792
+ return { label: s.name, type: "variable", detail };
793
+ });
794
+ }
795
+ var CURATED_PROPERTIES = {
796
+ console: [
797
+ snippetCompletion("log(${1:message})", {
798
+ label: "log",
799
+ type: "method",
800
+ detail: "(...args: any[]) -> void",
801
+ info: "Log to console"
802
+ }),
803
+ snippetCompletion("error(${1:message})", {
804
+ label: "error",
805
+ type: "method",
806
+ detail: "(...args: any[]) -> void",
807
+ info: "Log error"
808
+ }),
809
+ snippetCompletion("warn(${1:message})", {
810
+ label: "warn",
811
+ type: "method",
812
+ detail: "(...args: any[]) -> void",
813
+ info: "Log warning"
814
+ }),
815
+ snippetCompletion("info(${1:message})", {
816
+ label: "info",
817
+ type: "method",
818
+ detail: "(...args: any[]) -> void",
819
+ info: "Log info"
820
+ }),
821
+ snippetCompletion("debug(${1:message})", {
822
+ label: "debug",
823
+ type: "method",
824
+ detail: "(...args: any[]) -> void",
825
+ info: "Log debug"
826
+ }),
827
+ snippetCompletion("table(${1:data})", {
828
+ label: "table",
829
+ type: "method",
830
+ detail: "(data: any) -> void",
831
+ info: "Display as table"
832
+ }),
833
+ snippetCompletion("time('${1:label}')", {
834
+ label: "time",
835
+ type: "method",
836
+ detail: "(label: string) -> void",
837
+ info: "Start timer"
838
+ }),
839
+ snippetCompletion("timeEnd('${1:label}')", {
840
+ label: "timeEnd",
841
+ type: "method",
842
+ detail: "(label: string) -> void",
843
+ info: "End timer"
844
+ }),
845
+ snippetCompletion("group('${1:label}')", {
846
+ label: "group",
847
+ type: "method",
848
+ detail: "(label?: string) -> void",
849
+ info: "Start group"
850
+ }),
851
+ {
852
+ label: "groupEnd",
853
+ type: "method",
854
+ detail: "() -> void",
855
+ info: "End group"
856
+ },
857
+ {
858
+ label: "clear",
859
+ type: "method",
860
+ detail: "() -> void",
861
+ info: "Clear console"
862
+ }
863
+ ],
864
+ Math: [
865
+ // Common operations
866
+ snippetCompletion("floor(${1:x})", {
867
+ label: "floor",
868
+ type: "method",
869
+ detail: "(x: number) -> number",
870
+ info: "Round down"
871
+ }),
872
+ snippetCompletion("ceil(${1:x})", {
873
+ label: "ceil",
874
+ type: "method",
875
+ detail: "(x: number) -> number",
876
+ info: "Round up"
877
+ }),
878
+ snippetCompletion("round(${1:x})", {
879
+ label: "round",
880
+ type: "method",
881
+ detail: "(x: number) -> number",
882
+ info: "Round to nearest"
883
+ }),
884
+ snippetCompletion("trunc(${1:x})", {
885
+ label: "trunc",
886
+ type: "method",
887
+ detail: "(x: number) -> number",
888
+ info: "Remove decimals"
889
+ }),
890
+ snippetCompletion("abs(${1:x})", {
891
+ label: "abs",
892
+ type: "method",
893
+ detail: "(x: number) -> number",
894
+ info: "Absolute value"
895
+ }),
896
+ snippetCompletion("sign(${1:x})", {
897
+ label: "sign",
898
+ type: "method",
899
+ detail: "(x: number) -> number",
900
+ info: "Sign of number (-1, 0, 1)"
901
+ }),
902
+ // Min/max
903
+ snippetCompletion("min(${1:a}, ${2:b})", {
904
+ label: "min",
905
+ type: "method",
906
+ detail: "(...values: number[]) -> number",
907
+ info: "Minimum value"
908
+ }),
909
+ snippetCompletion("max(${1:a}, ${2:b})", {
910
+ label: "max",
911
+ type: "method",
912
+ detail: "(...values: number[]) -> number",
913
+ info: "Maximum value"
914
+ }),
915
+ snippetCompletion("clamp(${1:x}, ${2:min}, ${3:max})", {
916
+ label: "clamp",
917
+ type: "method",
918
+ detail: "(x, min, max) -> number",
919
+ info: "Clamp to range (ES2024)"
920
+ }),
921
+ // Powers and roots
922
+ snippetCompletion("pow(${1:base}, ${2:exp})", {
923
+ label: "pow",
924
+ type: "method",
925
+ detail: "(base, exp) -> number",
926
+ info: "Power"
927
+ }),
928
+ snippetCompletion("sqrt(${1:x})", {
929
+ label: "sqrt",
930
+ type: "method",
931
+ detail: "(x: number) -> number",
932
+ info: "Square root"
933
+ }),
934
+ snippetCompletion("cbrt(${1:x})", {
935
+ label: "cbrt",
936
+ type: "method",
937
+ detail: "(x: number) -> number",
938
+ info: "Cube root"
939
+ }),
940
+ snippetCompletion("hypot(${1:a}, ${2:b})", {
941
+ label: "hypot",
942
+ type: "method",
943
+ detail: "(...values: number[]) -> number",
944
+ info: "Hypotenuse"
945
+ }),
946
+ // Logarithms
947
+ snippetCompletion("log(${1:x})", {
948
+ label: "log",
949
+ type: "method",
950
+ detail: "(x: number) -> number",
951
+ info: "Natural log"
952
+ }),
953
+ snippetCompletion("log10(${1:x})", {
954
+ label: "log10",
955
+ type: "method",
956
+ detail: "(x: number) -> number",
957
+ info: "Base 10 log"
958
+ }),
959
+ snippetCompletion("log2(${1:x})", {
960
+ label: "log2",
961
+ type: "method",
962
+ detail: "(x: number) -> number",
963
+ info: "Base 2 log"
964
+ }),
965
+ snippetCompletion("exp(${1:x})", {
966
+ label: "exp",
967
+ type: "method",
968
+ detail: "(x: number) -> number",
969
+ info: "e^x"
970
+ }),
971
+ // Trig
972
+ snippetCompletion("sin(${1:x})", {
973
+ label: "sin",
974
+ type: "method",
975
+ detail: "(radians: number) -> number"
976
+ }),
977
+ snippetCompletion("cos(${1:x})", {
978
+ label: "cos",
979
+ type: "method",
980
+ detail: "(radians: number) -> number"
981
+ }),
982
+ snippetCompletion("tan(${1:x})", {
983
+ label: "tan",
984
+ type: "method",
985
+ detail: "(radians: number) -> number"
986
+ }),
987
+ snippetCompletion("atan2(${1:y}, ${2:x})", {
988
+ label: "atan2",
989
+ type: "method",
990
+ detail: "(y, x) -> number",
991
+ info: "Angle in radians"
992
+ }),
993
+ // Random
994
+ {
995
+ label: "random",
996
+ type: "method",
997
+ detail: "() -> number",
998
+ info: "Random 0-1"
999
+ },
1000
+ // Constants
1001
+ { label: "PI", type: "property", detail: "number", info: "3.14159..." },
1002
+ { label: "E", type: "property", detail: "number", info: "2.71828..." }
1003
+ ],
1004
+ JSON: [
1005
+ snippetCompletion("parse(${1:text})", {
1006
+ label: "parse",
1007
+ type: "method",
1008
+ detail: "(text: string) -> any",
1009
+ info: "Parse JSON string"
1010
+ }),
1011
+ snippetCompletion("stringify(${1:value})", {
1012
+ label: "stringify",
1013
+ type: "method",
1014
+ detail: "(value: any, replacer?, space?) -> string",
1015
+ info: "Convert to JSON"
1016
+ })
1017
+ ],
1018
+ Object: [
1019
+ snippetCompletion("keys(${1:obj})", {
1020
+ label: "keys",
1021
+ type: "method",
1022
+ detail: "(obj: object) -> string[]",
1023
+ info: "Get property names"
1024
+ }),
1025
+ snippetCompletion("values(${1:obj})", {
1026
+ label: "values",
1027
+ type: "method",
1028
+ detail: "(obj: object) -> any[]",
1029
+ info: "Get property values"
1030
+ }),
1031
+ snippetCompletion("entries(${1:obj})", {
1032
+ label: "entries",
1033
+ type: "method",
1034
+ detail: "(obj: object) -> [string, any][]",
1035
+ info: "Get key-value pairs"
1036
+ }),
1037
+ snippetCompletion("fromEntries(${1:entries})", {
1038
+ label: "fromEntries",
1039
+ type: "method",
1040
+ detail: "(entries: [string, any][]) -> object",
1041
+ info: "Create from entries"
1042
+ }),
1043
+ snippetCompletion("assign(${1:target}, ${2:source})", {
1044
+ label: "assign",
1045
+ type: "method",
1046
+ detail: "(target, ...sources) -> object",
1047
+ info: "Copy properties"
1048
+ }),
1049
+ snippetCompletion("hasOwn(${1:obj}, ${2:prop})", {
1050
+ label: "hasOwn",
1051
+ type: "method",
1052
+ detail: "(obj, prop: string) -> boolean",
1053
+ info: "Has own property"
1054
+ }),
1055
+ snippetCompletion("freeze(${1:obj})", {
1056
+ label: "freeze",
1057
+ type: "method",
1058
+ detail: "(obj: T) -> T",
1059
+ info: "Make immutable"
1060
+ })
1061
+ ],
1062
+ Array: [
1063
+ snippetCompletion("isArray(${1:value})", {
1064
+ label: "isArray",
1065
+ type: "method",
1066
+ detail: "(value: any) -> boolean",
1067
+ info: "Check if array"
1068
+ }),
1069
+ snippetCompletion("from(${1:iterable})", {
1070
+ label: "from",
1071
+ type: "method",
1072
+ detail: "(iterable, mapFn?) -> any[]",
1073
+ info: "Create from iterable"
1074
+ }),
1075
+ snippetCompletion("of(${1:items})", {
1076
+ label: "of",
1077
+ type: "method",
1078
+ detail: "(...items) -> any[]",
1079
+ info: "Create from arguments"
1080
+ })
1081
+ ],
1082
+ String: [
1083
+ snippetCompletion("fromCharCode(${1:code})", {
1084
+ label: "fromCharCode",
1085
+ type: "method",
1086
+ detail: "(...codes: number[]) -> string"
1087
+ }),
1088
+ snippetCompletion("fromCodePoint(${1:code})", {
1089
+ label: "fromCodePoint",
1090
+ type: "method",
1091
+ detail: "(...codes: number[]) -> string"
1092
+ })
1093
+ ],
1094
+ Number: [
1095
+ snippetCompletion("isFinite(${1:value})", {
1096
+ label: "isFinite",
1097
+ type: "method",
1098
+ detail: "(value: any) -> boolean"
1099
+ }),
1100
+ snippetCompletion("isInteger(${1:value})", {
1101
+ label: "isInteger",
1102
+ type: "method",
1103
+ detail: "(value: any) -> boolean"
1104
+ }),
1105
+ snippetCompletion("isNaN(${1:value})", {
1106
+ label: "isNaN",
1107
+ type: "method",
1108
+ detail: "(value: any) -> boolean"
1109
+ }),
1110
+ snippetCompletion("parseFloat(${1:string})", {
1111
+ label: "parseFloat",
1112
+ type: "method",
1113
+ detail: "(string: string) -> number"
1114
+ }),
1115
+ snippetCompletion("parseInt(${1:string})", {
1116
+ label: "parseInt",
1117
+ type: "method",
1118
+ detail: "(string: string, radix?) -> number"
1119
+ }),
1120
+ {
1121
+ label: "MAX_SAFE_INTEGER",
1122
+ type: "property",
1123
+ detail: "number",
1124
+ info: "2^53 - 1"
1125
+ },
1126
+ {
1127
+ label: "MIN_SAFE_INTEGER",
1128
+ type: "property",
1129
+ detail: "number",
1130
+ info: "-(2^53 - 1)"
1131
+ },
1132
+ {
1133
+ label: "EPSILON",
1134
+ type: "property",
1135
+ detail: "number",
1136
+ info: "Smallest difference"
1137
+ }
1138
+ ],
1139
+ Date: [
1140
+ {
1141
+ label: "now",
1142
+ type: "method",
1143
+ detail: "() -> number",
1144
+ info: "Current timestamp"
1145
+ },
1146
+ snippetCompletion("parse(${1:dateString})", {
1147
+ label: "parse",
1148
+ type: "method",
1149
+ detail: "(dateString: string) -> number"
1150
+ }),
1151
+ snippetCompletion("UTC(${1:year}, ${2:month})", {
1152
+ label: "UTC",
1153
+ type: "method",
1154
+ detail: "(year, month, ...) -> number"
1155
+ })
1156
+ ],
1157
+ Promise: [
1158
+ snippetCompletion("resolve(${1:value})", {
1159
+ label: "resolve",
1160
+ type: "method",
1161
+ detail: "(value: T) -> Promise<T>"
1162
+ }),
1163
+ snippetCompletion("reject(${1:reason})", {
1164
+ label: "reject",
1165
+ type: "method",
1166
+ detail: "(reason: any) -> Promise<never>"
1167
+ }),
1168
+ snippetCompletion("all(${1:promises})", {
1169
+ label: "all",
1170
+ type: "method",
1171
+ detail: "(promises: Promise[]) -> Promise<any[]>",
1172
+ info: "Wait for all"
1173
+ }),
1174
+ snippetCompletion("allSettled(${1:promises})", {
1175
+ label: "allSettled",
1176
+ type: "method",
1177
+ detail: "(promises: Promise[]) -> Promise<Result[]>",
1178
+ info: "Wait for all to settle"
1179
+ }),
1180
+ snippetCompletion("race(${1:promises})", {
1181
+ label: "race",
1182
+ type: "method",
1183
+ detail: "(promises: Promise[]) -> Promise<any>",
1184
+ info: "First to resolve/reject"
1185
+ }),
1186
+ snippetCompletion("any(${1:promises})", {
1187
+ label: "any",
1188
+ type: "method",
1189
+ detail: "(promises: Promise[]) -> Promise<any>",
1190
+ info: "First to resolve"
1191
+ })
1192
+ ]
1193
+ };
1194
+ var INTROSPECTABLE_GLOBALS = {
1195
+ // Core JS globals (always available)
1196
+ console,
1197
+ Math,
1198
+ JSON,
1199
+ Object,
1200
+ Array,
1201
+ String,
1202
+ Number,
1203
+ Boolean,
1204
+ Date,
1205
+ RegExp,
1206
+ Map,
1207
+ Set,
1208
+ WeakMap,
1209
+ WeakSet,
1210
+ Promise,
1211
+ Reflect,
1212
+ Proxy,
1213
+ Symbol,
1214
+ Error,
1215
+ TypeError,
1216
+ RangeError,
1217
+ SyntaxError,
1218
+ ReferenceError,
1219
+ ArrayBuffer,
1220
+ Uint8Array,
1221
+ Int8Array,
1222
+ Uint16Array,
1223
+ Int16Array,
1224
+ Uint32Array,
1225
+ Int32Array,
1226
+ Float32Array,
1227
+ Float64Array,
1228
+ Intl
1229
+ };
1230
+ if (typeof globalThis !== "undefined") {
1231
+ if (typeof crypto !== "undefined") INTROSPECTABLE_GLOBALS.crypto = crypto;
1232
+ if (typeof navigator !== "undefined")
1233
+ INTROSPECTABLE_GLOBALS.navigator = navigator;
1234
+ if (typeof localStorage !== "undefined")
1235
+ INTROSPECTABLE_GLOBALS.localStorage = localStorage;
1236
+ if (typeof sessionStorage !== "undefined")
1237
+ INTROSPECTABLE_GLOBALS.sessionStorage = sessionStorage;
1238
+ if (typeof fetch !== "undefined") INTROSPECTABLE_GLOBALS.fetch = fetch;
1239
+ if (typeof URL !== "undefined") INTROSPECTABLE_GLOBALS.URL = URL;
1240
+ if (typeof URLSearchParams !== "undefined")
1241
+ INTROSPECTABLE_GLOBALS.URLSearchParams = URLSearchParams;
1242
+ if (typeof Headers !== "undefined") INTROSPECTABLE_GLOBALS.Headers = Headers;
1243
+ if (typeof Request !== "undefined") INTROSPECTABLE_GLOBALS.Request = Request;
1244
+ if (typeof Response !== "undefined")
1245
+ INTROSPECTABLE_GLOBALS.Response = Response;
1246
+ if (typeof FormData !== "undefined")
1247
+ INTROSPECTABLE_GLOBALS.FormData = FormData;
1248
+ if (typeof Blob !== "undefined") INTROSPECTABLE_GLOBALS.Blob = Blob;
1249
+ if (typeof File !== "undefined") INTROSPECTABLE_GLOBALS.File = File;
1250
+ if (typeof FileReader !== "undefined")
1251
+ INTROSPECTABLE_GLOBALS.FileReader = FileReader;
1252
+ if (typeof AbortController !== "undefined")
1253
+ INTROSPECTABLE_GLOBALS.AbortController = AbortController;
1254
+ if (typeof TextEncoder !== "undefined")
1255
+ INTROSPECTABLE_GLOBALS.TextEncoder = TextEncoder;
1256
+ if (typeof TextDecoder !== "undefined")
1257
+ INTROSPECTABLE_GLOBALS.TextDecoder = TextDecoder;
1258
+ if (typeof Element !== "undefined") INTROSPECTABLE_GLOBALS.Element = Element;
1259
+ if (typeof HTMLElement !== "undefined")
1260
+ INTROSPECTABLE_GLOBALS.HTMLElement = HTMLElement;
1261
+ if (typeof Document !== "undefined")
1262
+ INTROSPECTABLE_GLOBALS.Document = Document;
1263
+ if (typeof Node !== "undefined") INTROSPECTABLE_GLOBALS.Node = Node;
1264
+ if (typeof Event !== "undefined") INTROSPECTABLE_GLOBALS.Event = Event;
1265
+ if (typeof CustomEvent !== "undefined")
1266
+ INTROSPECTABLE_GLOBALS.CustomEvent = CustomEvent;
1267
+ if (typeof MutationObserver !== "undefined")
1268
+ INTROSPECTABLE_GLOBALS.MutationObserver = MutationObserver;
1269
+ if (typeof ResizeObserver !== "undefined")
1270
+ INTROSPECTABLE_GLOBALS.ResizeObserver = ResizeObserver;
1271
+ if (typeof IntersectionObserver !== "undefined")
1272
+ INTROSPECTABLE_GLOBALS.IntersectionObserver = IntersectionObserver;
1273
+ if (typeof CanvasRenderingContext2D !== "undefined")
1274
+ INTROSPECTABLE_GLOBALS.CanvasRenderingContext2D = CanvasRenderingContext2D;
1275
+ if (typeof ImageData !== "undefined")
1276
+ INTROSPECTABLE_GLOBALS.ImageData = ImageData;
1277
+ if (typeof AudioContext !== "undefined")
1278
+ INTROSPECTABLE_GLOBALS.AudioContext = AudioContext;
1279
+ if (typeof performance !== "undefined")
1280
+ INTROSPECTABLE_GLOBALS.performance = performance;
1281
+ if (typeof PerformanceObserver !== "undefined")
1282
+ INTROSPECTABLE_GLOBALS.PerformanceObserver = PerformanceObserver;
1283
+ if (typeof document !== "undefined")
1284
+ INTROSPECTABLE_GLOBALS.document = document;
1285
+ if (typeof window !== "undefined") INTROSPECTABLE_GLOBALS.window = window;
1286
+ }
1287
+ function introspectObject(obj) {
1288
+ if (!obj || typeof obj !== "object" && typeof obj !== "function") {
1289
+ return [];
1290
+ }
1291
+ const completions = [];
1292
+ const seen = /* @__PURE__ */ new Set();
1293
+ try {
1294
+ const objectKeys = Object.keys(obj);
1295
+ if (objectKeys.length > 0) {
1296
+ for (const key of objectKeys) {
1297
+ if (key === "constructor" || key.startsWith("_") || seen.has(key))
1298
+ continue;
1299
+ seen.add(key);
1300
+ try {
1301
+ const value = obj[key];
1302
+ const valueType = typeof value;
1303
+ if (valueType === "function") {
1304
+ completions.push(
1305
+ snippetCompletion(`${key}(\${1})`, {
1306
+ label: key,
1307
+ type: "method",
1308
+ detail: "(...)"
1309
+ })
1310
+ );
1311
+ } else {
1312
+ completions.push({
1313
+ label: key,
1314
+ type: "property",
1315
+ detail: valueType
1316
+ });
1317
+ }
1318
+ } catch {
1319
+ }
1320
+ }
1321
+ }
1322
+ } catch {
1323
+ }
1324
+ let current = obj;
1325
+ while (current && current !== Object.prototype && current !== Function.prototype) {
1326
+ const keys = Object.getOwnPropertyNames(current);
1327
+ for (const key of keys) {
1328
+ if (key === "constructor" || key.startsWith("_") || seen.has(key)) {
1329
+ continue;
1330
+ }
1331
+ seen.add(key);
1332
+ try {
1333
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
1334
+ const value = descriptor?.value ?? (descriptor?.get ? "[getter]" : void 0);
1335
+ const valueType = typeof value;
1336
+ if (valueType === "function") {
1337
+ const fn = value;
1338
+ const paramCount = fn.length;
1339
+ const params = paramCount > 0 ? Array.from(
1340
+ { length: paramCount },
1341
+ (_, i) => `arg${i + 1}`
1342
+ ).join(", ") : "";
1343
+ completions.push(
1344
+ snippetCompletion(`${key}(${paramCount > 0 ? "${1}" : ""})`, {
1345
+ label: key,
1346
+ type: "method",
1347
+ detail: `(${params})`,
1348
+ boost: key.startsWith("to") ? -1 : 0
1349
+ // Demote toString, etc.
1350
+ })
1351
+ );
1352
+ } else {
1353
+ completions.push({
1354
+ label: key,
1355
+ type: "property",
1356
+ detail: valueType
1357
+ });
1358
+ }
1359
+ } catch {
1360
+ }
1361
+ }
1362
+ current = Object.getPrototypeOf(current);
1363
+ }
1364
+ return completions;
1365
+ }
1366
+ function getPropertyCompletions(objName) {
1367
+ if (CURATED_PROPERTIES[objName]) {
1368
+ return CURATED_PROPERTIES[objName];
1369
+ }
1370
+ const obj = INTROSPECTABLE_GLOBALS[objName];
1371
+ if (!obj) return [];
1372
+ return introspectObject(obj);
1373
+ }
1374
+ function getCompletionsFromLiveBinding(name, liveBindings) {
1375
+ if (liveBindings && name in liveBindings) {
1376
+ return introspectObject(liveBindings[name]);
1377
+ }
1378
+ const elementTypeMap = {
1379
+ // Common variable names that are likely specific element types
1380
+ div: "HTMLDivElement",
1381
+ span: "HTMLSpanElement",
1382
+ input: "HTMLInputElement",
1383
+ button: "HTMLButtonElement",
1384
+ form: "HTMLFormElement",
1385
+ img: "HTMLImageElement",
1386
+ link: "HTMLLinkElement",
1387
+ anchor: "HTMLAnchorElement",
1388
+ table: "HTMLTableElement",
1389
+ canvas: "HTMLCanvasElement",
1390
+ video: "HTMLVideoElement",
1391
+ audio: "HTMLAudioElement",
1392
+ select: "HTMLSelectElement",
1393
+ textarea: "HTMLTextAreaElement"
1394
+ // iframe, etc.
1395
+ };
1396
+ const lowerName = name.toLowerCase();
1397
+ const elementType = elementTypeMap[lowerName];
1398
+ if (elementType && typeof globalThis !== "undefined") {
1399
+ const ElementClass = globalThis[elementType];
1400
+ if (ElementClass?.prototype) {
1401
+ return introspectObject(ElementClass.prototype);
1402
+ }
1403
+ }
1404
+ return [];
1405
+ }
1406
+ function getPathBeforeDot(source, dotPos) {
1407
+ const before = source.slice(0, dotPos);
1408
+ const match = before.match(
1409
+ /([A-Za-z_$][\w$]*(?:\s*\.\s*[A-Za-z_$][\w$]*)*)\s*$/
1410
+ );
1411
+ return match ? match[1].replace(/\s+/g, "") : null;
1412
+ }
1413
+ function resolvePath(path, bindings) {
1414
+ if (!bindings) return void 0;
1415
+ const parts = path.split(".");
1416
+ let value = bindings[parts[0]];
1417
+ for (let i = 1; i < parts.length && value != null; i++) {
1418
+ try {
1419
+ value = value[parts[i]];
1420
+ } catch {
1421
+ return void 0;
1422
+ }
1423
+ }
1424
+ return value;
1425
+ }
1426
+ function getCompletionsFromPath(path, liveBindings) {
1427
+ const value = resolvePath(path, liveBindings);
1428
+ if (value != null && (typeof value === "object" || typeof value === "function")) {
1429
+ return introspectObject(value);
1430
+ }
1431
+ if (!path.includes(".")) {
1432
+ return getCompletionsFromLiveBinding(path, liveBindings);
1433
+ }
1434
+ return [];
1435
+ }
1436
+ function getPlaceholderForParam(name, info) {
1437
+ if (info.example !== void 0 && info.example !== null) {
1438
+ const ex = info.example;
1439
+ if (typeof ex === "string") return `'${ex}'`;
1440
+ if (typeof ex === "number" || typeof ex === "boolean") return String(ex);
1441
+ if (Array.isArray(ex)) return JSON.stringify(ex);
1442
+ if (typeof ex === "object") return JSON.stringify(ex);
1443
+ return String(ex);
1444
+ }
1445
+ const examples = info.type?.examples || info.examples;
1446
+ if (Array.isArray(examples) && examples.length > 0) {
1447
+ const ex = examples[0];
1448
+ if (typeof ex === "string") return `'${ex}'`;
1449
+ if (typeof ex === "number" || typeof ex === "boolean") return String(ex);
1450
+ if (Array.isArray(ex)) return JSON.stringify(ex);
1451
+ if (typeof ex === "object") return JSON.stringify(ex);
1452
+ return String(ex);
1453
+ }
1454
+ if (info.default !== void 0 && info.default !== null) {
1455
+ const def = info.default;
1456
+ if (typeof def === "string") return `'${def}'`;
1457
+ if (typeof def === "number" || typeof def === "boolean") return String(def);
1458
+ if (Array.isArray(def)) return JSON.stringify(def);
1459
+ if (typeof def === "object") return JSON.stringify(def);
1460
+ return String(def);
1461
+ }
1462
+ const kind = info.type?.kind || info.type?.type || "any";
1463
+ switch (kind) {
1464
+ case "string":
1465
+ return `'${name}'`;
1466
+ case "number":
1467
+ return "0";
1468
+ case "boolean":
1469
+ return "true";
1470
+ case "null":
1471
+ return "null";
1472
+ case "array":
1473
+ return "[]";
1474
+ case "object":
1475
+ return "{}";
1476
+ default:
1477
+ return name;
1478
+ }
1479
+ }
1480
+ function tjsCompletionSource(config = {}) {
1481
+ return async (context) => {
1482
+ try {
1483
+ const word = context.matchBefore(/[\w$]*/);
1484
+ if (!word) return null;
1485
+ const source = context.state.doc.toString();
1486
+ const pos = context.pos;
1487
+ const skipRegions = findSkipRegions(source);
1488
+ if (isInSkipRegion(pos, skipRegions)) {
1489
+ return null;
1490
+ }
1491
+ const lineStart = context.state.doc.lineAt(pos).from;
1492
+ const lineBefore = source.slice(lineStart, word.from);
1493
+ const charBefore = source.slice(Math.max(0, word.from - 1), word.from);
1494
+ if (word.from === word.to && !context.explicit && charBefore !== ".") {
1495
+ return null;
1496
+ }
1497
+ let options = [];
1498
+ if (charBefore === ".") {
1499
+ const before = source.slice(Math.max(0, word.from - 50), word.from);
1500
+ if (/expect\s*\([^)]*\)\s*\.$/.test(before)) {
1501
+ options = EXPECT_MATCHERS;
1502
+ } else {
1503
+ const path = getPathBeforeDot(source, word.from - 1);
1504
+ if (path) {
1505
+ if (!path.includes(".")) {
1506
+ options = getPropertyCompletions(path);
1507
+ }
1508
+ if (options.length === 0) {
1509
+ const liveBindings = config.getLiveBindings?.();
1510
+ options = getCompletionsFromPath(path, liveBindings);
1511
+ }
1512
+ if (options.length === 0 && config.getMembers) {
1513
+ const members = await config.getMembers(path);
1514
+ if (members && members.length) {
1515
+ options = members.map(memberToCompletion);
1516
+ }
1517
+ }
1518
+ }
1519
+ }
1520
+ } else if (/:\s*$/.test(lineBefore)) {
1521
+ options = TJS_TYPES;
1522
+ } else if (/->\s*$/.test(lineBefore)) {
1523
+ options = TJS_TYPES;
1524
+ } else {
1525
+ options = [
1526
+ ...TJS_COMPLETIONS,
1527
+ ...RUNTIME_COMPLETIONS,
1528
+ ...GLOBAL_COMPLETIONS,
1529
+ ...completionsFromScope(source, pos)
1530
+ ];
1531
+ const metadata = config.getMetadata?.();
1532
+ if (metadata) {
1533
+ for (const [name, meta] of Object.entries(metadata)) {
1534
+ const paramEntries = meta.params ? Object.entries(meta.params) : [];
1535
+ const paramList = paramEntries.map(([pName, pInfo]) => {
1536
+ const pType = pInfo.type?.kind || pInfo.type?.type || "any";
1537
+ const optional = !pInfo.required;
1538
+ return optional ? `${pName}?: ${pType}` : `${pName}: ${pType}`;
1539
+ }).join(", ");
1540
+ const snippetParams = paramEntries.map(([pName, pInfo], i) => {
1541
+ const placeholder = getPlaceholderForParam(pName, pInfo);
1542
+ return `\${${i + 1}:${placeholder}}`;
1543
+ }).join(", ");
1544
+ const returnType = meta.returns?.type || meta.returns?.kind || "void";
1545
+ let infoText = meta.description || "";
1546
+ for (const [pName, pInfo] of paramEntries) {
1547
+ const pExamples = pInfo.type?.examples || pInfo.examples;
1548
+ if (Array.isArray(pExamples) && pExamples.length > 0) {
1549
+ const formatted = pExamples.map(
1550
+ (ex) => typeof ex === "string" ? `'${ex}'` : String(ex)
1551
+ ).join(", ");
1552
+ infoText += `${infoText ? "\n" : ""}${pName}: e.g. ${formatted}`;
1553
+ }
1554
+ }
1555
+ options.push(
1556
+ snippetCompletion(`${name}(${snippetParams})`, {
1557
+ label: name,
1558
+ type: "function",
1559
+ detail: `(${paramList}) -> ${returnType}`,
1560
+ info: infoText || void 0,
1561
+ boost: 2
1562
+ // Boost user-defined functions above globals
1563
+ })
1564
+ );
1565
+ }
1566
+ }
1567
+ }
1568
+ if (options.length === 0) return null;
1569
+ return {
1570
+ from: word.from,
1571
+ options,
1572
+ validFor: /^[\w$]*$/
1573
+ };
1574
+ } catch (e) {
1575
+ console.warn("TJS autocomplete error:", e);
1576
+ return null;
1577
+ }
1578
+ };
1579
+ }
1580
+ function ajsEditorExtension(config = {}) {
1581
+ return [
1582
+ javascript({ jsx: config.jsx, typescript: config.typescript }),
1583
+ // Syntax highlighting comes from customSetup (defaultHighlightStyle with fallback)
1584
+ // or from the active theme (e.g. oneDark)
1585
+ forbiddenHighlighter,
1586
+ tryWithoutCatchHighlighter,
1587
+ ajsTheme,
1588
+ autocompletion({
1589
+ override: [tjsCompletionSource(config.autocomplete || {})],
1590
+ activateOnTyping: true
1591
+ })
1592
+ ];
1593
+ }
1594
+ function tjsEditorExtension(config = {}) {
1595
+ return [
1596
+ javascript({ jsx: config.jsx, typescript: config.typescript }),
1597
+ // Syntax highlighting comes from customSetup (defaultHighlightStyle with fallback)
1598
+ // or from the active theme (e.g. oneDark)
1599
+ tjsForbiddenHighlighter,
1600
+ // Use TJS forbidden list (more permissive)
1601
+ tryWithoutCatchHighlighter,
1602
+ ajsTheme,
1603
+ autocompletion({
1604
+ override: [tjsCompletionSource(config.autocomplete || {})],
1605
+ activateOnTyping: true
1606
+ })
1607
+ ];
1608
+ }
1609
+ function ajsLanguage(config = {}) {
1610
+ const jsLang = javascript({ jsx: config.jsx, typescript: config.typescript });
1611
+ return new LanguageSupport(jsLang.language, [
1612
+ forbiddenHighlighter,
1613
+ tryWithoutCatchHighlighter,
1614
+ ajsTheme
1615
+ ]);
287
1616
  }
1617
+ export {
1618
+ FORBIDDEN_KEYWORDS3 as FORBIDDEN_KEYWORDS,
1619
+ ajsEditorExtension as ajs,
1620
+ ajsEditorExtension,
1621
+ ajsLanguage,
1622
+ tjsCompletionSource,
1623
+ tjsEditorExtension
1624
+ };