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.
- package/CLAUDE.md +12 -4
- package/dist/experiments/ambient/probe.d.ts +52 -0
- package/dist/index.js +115 -171
- package/dist/index.js.map +4 -4
- package/dist/scripts/build-editors.d.ts +19 -0
- package/dist/src/css/dimensions.d.ts +23 -0
- package/dist/src/css/index.d.ts +85 -0
- package/dist/src/css/predicates.d.ts +38 -0
- package/dist/src/css/shorthands.d.ts +31 -0
- package/dist/src/css/style.d.ts +48 -0
- package/dist/src/lang/emitters/js.d.ts +9 -1
- package/dist/src/lang/index.d.ts +2 -2
- package/dist/src/lang/parser-transforms.d.ts +9 -5
- package/dist/src/lang/parser.d.ts +2 -0
- package/dist/src/lang/predicate.d.ts +60 -0
- package/dist/src/lang/transpiler.d.ts +3 -2
- package/dist/src/lang/types.d.ts +16 -0
- package/dist/src/schema/index.d.ts +12 -0
- package/dist/tjs-browser-from-ts.js +20 -20
- package/dist/tjs-browser-from-ts.js.map +3 -3
- package/dist/tjs-browser.js +100 -90
- package/dist/tjs-browser.js.map +4 -4
- package/dist/tjs-css.js +193 -0
- package/dist/tjs-css.js.map +7 -0
- package/dist/tjs-eval.js +43 -42
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +13 -13
- package/dist/tjs-from-ts.js.map +2 -2
- package/dist/tjs-lang.js +102 -92
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-runtime.js +10 -0
- package/dist/tjs-runtime.js.map +7 -0
- package/dist/tjs-schema.js +6 -0
- package/dist/tjs-schema.js.map +7 -0
- package/dist/tjs-vm.js +55 -54
- package/dist/tjs-vm.js.map +4 -4
- package/docs/ambient-contracts.md +261 -0
- package/editors/ace/ajs-mode.js +214 -233
- package/editors/codemirror/ajs-language.js +1570 -233
- package/editors/editors-build.test.ts +29 -0
- package/editors/monaco/ajs-monarch.js +239 -195
- package/llms.txt +4 -0
- package/package.json +35 -4
- package/src/css/css.test.ts +122 -0
- package/src/css/dimensions.test.ts +112 -0
- package/src/css/dimensions.ts +146 -0
- package/src/css/index.ts +243 -0
- package/src/css/perf.bench.test.ts +109 -0
- package/src/css/predicates.ts +232 -0
- package/src/css/property-aware.test.ts +84 -0
- package/src/css/shorthands.test.ts +90 -0
- package/src/css/shorthands.ts +113 -0
- package/src/css/style.test.ts +125 -0
- package/src/css/style.ts +134 -0
- package/src/index-tsfree.test.ts +58 -0
- package/src/lang/emit-verified-predicate.test.ts +95 -0
- package/src/lang/emitters/dts.test.ts +31 -0
- package/src/lang/emitters/dts.ts +23 -4
- package/src/lang/emitters/js.ts +33 -1
- package/src/lang/from-ts.test.ts +1 -1
- package/src/lang/generic-verified-predicate.test.ts +39 -0
- package/src/lang/index.ts +14 -5
- package/src/lang/parser-transforms.ts +109 -14
- package/src/lang/parser.ts +9 -2
- package/src/lang/predicate-evaluator.test.ts +49 -0
- package/src/lang/predicate-report.test.ts +54 -0
- package/src/lang/predicate.ts +268 -0
- package/src/lang/redos-lint.test.ts +81 -0
- package/src/lang/transpiler.ts +13 -0
- package/src/lang/type-verified-predicate.test.ts +83 -0
- package/src/lang/types.ts +17 -0
- package/src/lang/typescript-syntax.test.ts +2 -1
- package/src/schema/index.ts +62 -0
- package/src/schema/schema.test.ts +66 -0
- package/src/use-cases/bootstrap.test.ts +14 -4
|
@@ -1,287 +1,1624 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
i
|
|
138
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
i += 2
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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
|
|
160
|
-
continue
|
|
366
|
+
if (doc[i] === "\\") {
|
|
367
|
+
i += 2;
|
|
368
|
+
continue;
|
|
161
369
|
}
|
|
162
|
-
if (doc[i] ===
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
break
|
|
370
|
+
if (doc[i] === "`") {
|
|
371
|
+
regions.push([stringStart, i + 1]);
|
|
372
|
+
i++;
|
|
373
|
+
break;
|
|
167
374
|
}
|
|
168
|
-
if (doc[i] ===
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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] ===
|
|
176
|
-
else if (doc[i] ===
|
|
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
|
|
180
|
-
stringStart = i
|
|
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
|
|
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] ===
|
|
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
|
|
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
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
{
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
+
};
|