mrmd-js 2.0.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/README.md +842 -0
- package/dist/index.cjs +7613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +7530 -0
- package/dist/index.js.map +1 -0
- package/dist/mrmd-js.iife.js +7618 -0
- package/dist/mrmd-js.iife.js.map +1 -0
- package/package.json +47 -0
- package/src/analysis/format.js +371 -0
- package/src/analysis/index.js +18 -0
- package/src/analysis/is-complete.js +394 -0
- package/src/constants.js +44 -0
- package/src/execute/css.js +205 -0
- package/src/execute/html.js +162 -0
- package/src/execute/index.js +41 -0
- package/src/execute/interface.js +144 -0
- package/src/execute/javascript.js +197 -0
- package/src/execute/registry.js +245 -0
- package/src/index.js +136 -0
- package/src/lsp/complete.js +353 -0
- package/src/lsp/format.js +310 -0
- package/src/lsp/hover.js +126 -0
- package/src/lsp/index.js +55 -0
- package/src/lsp/inspect.js +466 -0
- package/src/lsp/parse.js +455 -0
- package/src/lsp/variables.js +283 -0
- package/src/runtime.js +518 -0
- package/src/session/console-capture.js +181 -0
- package/src/session/context/iframe.js +407 -0
- package/src/session/context/index.js +12 -0
- package/src/session/context/interface.js +38 -0
- package/src/session/context/main.js +357 -0
- package/src/session/index.js +16 -0
- package/src/session/manager.js +327 -0
- package/src/session/session.js +678 -0
- package/src/transform/async.js +133 -0
- package/src/transform/extract.js +251 -0
- package/src/transform/index.js +10 -0
- package/src/transform/persistence.js +176 -0
- package/src/types/analysis.js +24 -0
- package/src/types/capabilities.js +44 -0
- package/src/types/completion.js +47 -0
- package/src/types/execution.js +62 -0
- package/src/types/index.js +16 -0
- package/src/types/inspection.js +39 -0
- package/src/types/session.js +32 -0
- package/src/types/streaming.js +74 -0
- package/src/types/variables.js +54 -0
- package/src/utils/ansi-renderer.js +301 -0
- package/src/utils/css-applicator.js +149 -0
- package/src/utils/html-renderer.js +355 -0
- package/src/utils/index.js +25 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async Transform
|
|
3
|
+
*
|
|
4
|
+
* Wraps code to support top-level await.
|
|
5
|
+
* @module transform/async
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Check if code contains top-level await
|
|
10
|
+
* @param {string} code
|
|
11
|
+
* @returns {boolean}
|
|
12
|
+
*/
|
|
13
|
+
function hasTopLevelAwait(code) {
|
|
14
|
+
// Simple check - look for await outside of async function/arrow
|
|
15
|
+
// This is a heuristic; a proper check would need AST parsing
|
|
16
|
+
|
|
17
|
+
// Remove strings, comments, and regex to avoid false positives
|
|
18
|
+
const cleaned = code
|
|
19
|
+
// Remove template literals (simple version)
|
|
20
|
+
.replace(/`[^`]*`/g, '')
|
|
21
|
+
// Remove strings
|
|
22
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, '')
|
|
23
|
+
.replace(/'(?:[^'\\]|\\.)*'/g, '')
|
|
24
|
+
// Remove single-line comments
|
|
25
|
+
.replace(/\/\/[^\n]*/g, '')
|
|
26
|
+
// Remove multi-line comments
|
|
27
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
28
|
+
|
|
29
|
+
// Track nesting depth of async contexts
|
|
30
|
+
// This is simplified - real implementation would use AST
|
|
31
|
+
let depth = 0;
|
|
32
|
+
let i = 0;
|
|
33
|
+
|
|
34
|
+
while (i < cleaned.length) {
|
|
35
|
+
// Check for async function or async arrow
|
|
36
|
+
if (cleaned.slice(i, i + 5) === 'async') {
|
|
37
|
+
// Look ahead for function or arrow
|
|
38
|
+
let j = i + 5;
|
|
39
|
+
while (j < cleaned.length && /\s/.test(cleaned[j])) j++;
|
|
40
|
+
|
|
41
|
+
if (
|
|
42
|
+
cleaned.slice(j, j + 8) === 'function' ||
|
|
43
|
+
cleaned[j] === '('
|
|
44
|
+
) {
|
|
45
|
+
// Found async context start
|
|
46
|
+
depth++;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Track braces for context depth (simplified)
|
|
51
|
+
if (cleaned[i] === '{') {
|
|
52
|
+
// Already in async context, depth increases
|
|
53
|
+
}
|
|
54
|
+
if (cleaned[i] === '}') {
|
|
55
|
+
// Could be end of async context
|
|
56
|
+
if (depth > 0) depth--;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check for await at top level
|
|
60
|
+
if (cleaned.slice(i, i + 5) === 'await') {
|
|
61
|
+
const before = i > 0 ? cleaned[i - 1] : ' ';
|
|
62
|
+
const after = i + 5 < cleaned.length ? cleaned[i + 5] : ' ';
|
|
63
|
+
|
|
64
|
+
// Check it's a word boundary
|
|
65
|
+
if (!/[a-zA-Z0-9_$]/.test(before) && !/[a-zA-Z0-9_$]/.test(after)) {
|
|
66
|
+
// Found await - check if we're at top level
|
|
67
|
+
// For simplicity, assume any await not deep in braces is top-level
|
|
68
|
+
// A proper implementation would track async function scopes
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Wrap code for top-level await support
|
|
81
|
+
*
|
|
82
|
+
* Transforms code to run in an async IIFE that captures the last expression.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} code - Source code
|
|
85
|
+
* @returns {string} Wrapped code
|
|
86
|
+
*/
|
|
87
|
+
export function wrapForAsync(code) {
|
|
88
|
+
const needsAsync = hasTopLevelAwait(code);
|
|
89
|
+
|
|
90
|
+
// We always wrap to capture the return value
|
|
91
|
+
// The wrapper captures the last expression value
|
|
92
|
+
|
|
93
|
+
if (needsAsync) {
|
|
94
|
+
return `(async () => {
|
|
95
|
+
${code}
|
|
96
|
+
})()`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return `(() => {
|
|
100
|
+
${code}
|
|
101
|
+
})()`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Wrap code and capture the last expression value
|
|
106
|
+
*
|
|
107
|
+
* @param {string} code - Source code
|
|
108
|
+
* @returns {string} Wrapped code that returns last expression
|
|
109
|
+
*/
|
|
110
|
+
export function wrapWithLastExpression(code) {
|
|
111
|
+
const needsAsync = hasTopLevelAwait(code);
|
|
112
|
+
|
|
113
|
+
// Find the last expression and make it a return value
|
|
114
|
+
// This is tricky without AST - we use eval trick instead
|
|
115
|
+
const wrapped = `
|
|
116
|
+
;(${needsAsync ? 'async ' : ''}function() {
|
|
117
|
+
let __result__;
|
|
118
|
+
try {
|
|
119
|
+
__result__ = eval(${JSON.stringify(code)});
|
|
120
|
+
} catch (e) {
|
|
121
|
+
if (e instanceof SyntaxError) {
|
|
122
|
+
// Code might be statements, not expression
|
|
123
|
+
eval(${JSON.stringify(code)});
|
|
124
|
+
__result__ = undefined;
|
|
125
|
+
} else {
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return __result__;
|
|
130
|
+
})()`;
|
|
131
|
+
|
|
132
|
+
return wrapped.trim();
|
|
133
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract Transform
|
|
3
|
+
*
|
|
4
|
+
* Extracts declared variable names from code.
|
|
5
|
+
* @module transform/extract
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Extract all variable names that will be declared by the code.
|
|
10
|
+
* Handles var, let, const, function, and class declarations.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} code - Source code
|
|
13
|
+
* @returns {string[]} Array of declared variable names
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* extractDeclaredVariables('const x = 1; let { a, b } = obj; function foo() {}')
|
|
17
|
+
* // Returns: ['x', 'a', 'b', 'foo']
|
|
18
|
+
*/
|
|
19
|
+
export function extractDeclaredVariables(code) {
|
|
20
|
+
const variables = new Set();
|
|
21
|
+
|
|
22
|
+
// Remove strings, comments to avoid false matches
|
|
23
|
+
const cleaned = removeStringsAndComments(code);
|
|
24
|
+
|
|
25
|
+
// Match var/let/const declarations
|
|
26
|
+
// Handles: const x = 1, let x = 1, var x = 1
|
|
27
|
+
// Handles: const { a, b } = obj, const [a, b] = arr
|
|
28
|
+
const varPattern = /\b(?:var|let|const)\s+([^=;]+?)(?:\s*=|\s*;|\s*$)/g;
|
|
29
|
+
|
|
30
|
+
let match;
|
|
31
|
+
while ((match = varPattern.exec(cleaned)) !== null) {
|
|
32
|
+
const declaration = match[1].trim();
|
|
33
|
+
extractNamesFromPattern(declaration, variables);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Match function declarations
|
|
37
|
+
const funcPattern = /\bfunction\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
|
|
38
|
+
while ((match = funcPattern.exec(cleaned)) !== null) {
|
|
39
|
+
variables.add(match[1]);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Match class declarations
|
|
43
|
+
const classPattern = /\bclass\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g;
|
|
44
|
+
while ((match = classPattern.exec(cleaned)) !== null) {
|
|
45
|
+
variables.add(match[1]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return Array.from(variables);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract variable names from a destructuring pattern or simple identifier
|
|
53
|
+
* @param {string} pattern
|
|
54
|
+
* @param {Set<string>} variables
|
|
55
|
+
*/
|
|
56
|
+
function extractNamesFromPattern(pattern, variables) {
|
|
57
|
+
// Simple identifier
|
|
58
|
+
const simpleMatch = pattern.match(/^([a-zA-Z_$][a-zA-Z0-9_$]*)$/);
|
|
59
|
+
if (simpleMatch) {
|
|
60
|
+
variables.add(simpleMatch[1]);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Object destructuring { a, b: c, ...rest }
|
|
65
|
+
if (pattern.startsWith('{')) {
|
|
66
|
+
const inner = pattern.slice(1, -1);
|
|
67
|
+
// Split by comma, handling nested braces
|
|
68
|
+
const parts = splitByComma(inner);
|
|
69
|
+
for (const part of parts) {
|
|
70
|
+
const trimmed = part.trim();
|
|
71
|
+
if (!trimmed) continue;
|
|
72
|
+
|
|
73
|
+
// Handle rest: ...rest
|
|
74
|
+
if (trimmed.startsWith('...')) {
|
|
75
|
+
const name = trimmed.slice(3).trim();
|
|
76
|
+
if (isValidIdentifier(name)) {
|
|
77
|
+
variables.add(name);
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Handle rename: key: name or key: pattern
|
|
83
|
+
const colonIdx = trimmed.indexOf(':');
|
|
84
|
+
if (colonIdx !== -1) {
|
|
85
|
+
const value = trimmed.slice(colonIdx + 1).trim();
|
|
86
|
+
extractNamesFromPattern(value, variables);
|
|
87
|
+
} else {
|
|
88
|
+
// Simple: key (which is also the variable name)
|
|
89
|
+
const name = trimmed.split('=')[0].trim(); // Handle default values
|
|
90
|
+
if (isValidIdentifier(name)) {
|
|
91
|
+
variables.add(name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Array destructuring [a, b, ...rest]
|
|
99
|
+
if (pattern.startsWith('[')) {
|
|
100
|
+
const inner = pattern.slice(1, -1);
|
|
101
|
+
const parts = splitByComma(inner);
|
|
102
|
+
for (const part of parts) {
|
|
103
|
+
const trimmed = part.trim();
|
|
104
|
+
if (!trimmed) continue;
|
|
105
|
+
|
|
106
|
+
// Handle rest: ...rest
|
|
107
|
+
if (trimmed.startsWith('...')) {
|
|
108
|
+
const name = trimmed.slice(3).trim();
|
|
109
|
+
if (isValidIdentifier(name)) {
|
|
110
|
+
variables.add(name);
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle nested destructuring or simple name
|
|
116
|
+
const nameOrPattern = trimmed.split('=')[0].trim();
|
|
117
|
+
extractNamesFromPattern(nameOrPattern, variables);
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Multiple declarations: a, b, c (from var a, b, c)
|
|
123
|
+
if (pattern.includes(',')) {
|
|
124
|
+
const parts = splitByComma(pattern);
|
|
125
|
+
for (const part of parts) {
|
|
126
|
+
const trimmed = part.trim().split('=')[0].trim();
|
|
127
|
+
if (isValidIdentifier(trimmed)) {
|
|
128
|
+
variables.add(trimmed);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Split string by commas, respecting nested brackets
|
|
136
|
+
* @param {string} str
|
|
137
|
+
* @returns {string[]}
|
|
138
|
+
*/
|
|
139
|
+
function splitByComma(str) {
|
|
140
|
+
const parts = [];
|
|
141
|
+
let current = '';
|
|
142
|
+
let depth = 0;
|
|
143
|
+
|
|
144
|
+
for (const char of str) {
|
|
145
|
+
if ((char === '{' || char === '[' || char === '(')) {
|
|
146
|
+
depth++;
|
|
147
|
+
current += char;
|
|
148
|
+
} else if ((char === '}' || char === ']' || char === ')')) {
|
|
149
|
+
depth--;
|
|
150
|
+
current += char;
|
|
151
|
+
} else if (char === ',' && depth === 0) {
|
|
152
|
+
parts.push(current);
|
|
153
|
+
current = '';
|
|
154
|
+
} else {
|
|
155
|
+
current += char;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (current) {
|
|
160
|
+
parts.push(current);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parts;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Check if string is a valid JavaScript identifier
|
|
168
|
+
* @param {string} name
|
|
169
|
+
* @returns {boolean}
|
|
170
|
+
*/
|
|
171
|
+
function isValidIdentifier(name) {
|
|
172
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Remove strings and comments from code
|
|
177
|
+
* @param {string} code
|
|
178
|
+
* @returns {string}
|
|
179
|
+
*/
|
|
180
|
+
function removeStringsAndComments(code) {
|
|
181
|
+
let result = '';
|
|
182
|
+
let i = 0;
|
|
183
|
+
|
|
184
|
+
while (i < code.length) {
|
|
185
|
+
// Single-line comment
|
|
186
|
+
if (code[i] === '/' && code[i + 1] === '/') {
|
|
187
|
+
while (i < code.length && code[i] !== '\n') i++;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Multi-line comment
|
|
192
|
+
if (code[i] === '/' && code[i + 1] === '*') {
|
|
193
|
+
i += 2;
|
|
194
|
+
while (i < code.length && !(code[i] === '*' && code[i + 1] === '/')) i++;
|
|
195
|
+
i += 2;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Template literal
|
|
200
|
+
if (code[i] === '`') {
|
|
201
|
+
result += ' ';
|
|
202
|
+
i++;
|
|
203
|
+
while (i < code.length) {
|
|
204
|
+
if (code[i] === '\\') {
|
|
205
|
+
i += 2;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (code[i] === '`') {
|
|
209
|
+
i++;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
if (code[i] === '$' && code[i + 1] === '{') {
|
|
213
|
+
i += 2;
|
|
214
|
+
let depth = 1;
|
|
215
|
+
while (i < code.length && depth > 0) {
|
|
216
|
+
if (code[i] === '{') depth++;
|
|
217
|
+
else if (code[i] === '}') depth--;
|
|
218
|
+
i++;
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
i++;
|
|
223
|
+
}
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// String
|
|
228
|
+
if (code[i] === '"' || code[i] === "'") {
|
|
229
|
+
const quote = code[i];
|
|
230
|
+
result += ' ';
|
|
231
|
+
i++;
|
|
232
|
+
while (i < code.length) {
|
|
233
|
+
if (code[i] === '\\') {
|
|
234
|
+
i += 2;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (code[i] === quote) {
|
|
238
|
+
i++;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
i++;
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
result += code[i];
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Transformations
|
|
3
|
+
*
|
|
4
|
+
* Utilities for transforming code for REPL execution.
|
|
5
|
+
* @module transform
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { transformForPersistence } from './persistence.js';
|
|
9
|
+
export { wrapForAsync, wrapWithLastExpression } from './async.js';
|
|
10
|
+
export { extractDeclaredVariables } from './extract.js';
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistence Transform
|
|
3
|
+
*
|
|
4
|
+
* Transforms const/let declarations to var for persistence across executions.
|
|
5
|
+
* In a REPL, we want variables to persist between cells. const/let are
|
|
6
|
+
* block-scoped and would be lost; var attaches to the global scope.
|
|
7
|
+
*
|
|
8
|
+
* @module transform/persistence
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Transform const/let declarations to var for persistence.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} code - Source code
|
|
15
|
+
* @returns {string} Transformed code
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* transformForPersistence('const x = 1; let y = 2;')
|
|
19
|
+
* // Returns: 'var x = 1; var y = 2;'
|
|
20
|
+
*/
|
|
21
|
+
export function transformForPersistence(code) {
|
|
22
|
+
// Use a state machine approach to avoid transforming inside strings/comments
|
|
23
|
+
let result = '';
|
|
24
|
+
let i = 0;
|
|
25
|
+
const len = code.length;
|
|
26
|
+
|
|
27
|
+
while (i < len) {
|
|
28
|
+
// Check for single-line comment
|
|
29
|
+
if (code[i] === '/' && code[i + 1] === '/') {
|
|
30
|
+
const start = i;
|
|
31
|
+
i += 2;
|
|
32
|
+
while (i < len && code[i] !== '\n') i++;
|
|
33
|
+
result += code.slice(start, i);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check for multi-line comment
|
|
38
|
+
if (code[i] === '/' && code[i + 1] === '*') {
|
|
39
|
+
const start = i;
|
|
40
|
+
i += 2;
|
|
41
|
+
while (i < len && !(code[i] === '*' && code[i + 1] === '/')) i++;
|
|
42
|
+
i += 2;
|
|
43
|
+
result += code.slice(start, i);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check for template literal
|
|
48
|
+
if (code[i] === '`') {
|
|
49
|
+
const start = i;
|
|
50
|
+
i++;
|
|
51
|
+
while (i < len) {
|
|
52
|
+
if (code[i] === '\\') {
|
|
53
|
+
i += 2;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (code[i] === '`') {
|
|
57
|
+
i++;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
// Handle ${...} - need to track nested braces
|
|
61
|
+
if (code[i] === '$' && code[i + 1] === '{') {
|
|
62
|
+
i += 2;
|
|
63
|
+
let braceDepth = 1;
|
|
64
|
+
while (i < len && braceDepth > 0) {
|
|
65
|
+
if (code[i] === '{') braceDepth++;
|
|
66
|
+
else if (code[i] === '}') braceDepth--;
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
i++;
|
|
72
|
+
}
|
|
73
|
+
result += code.slice(start, i);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for string (single or double quote)
|
|
78
|
+
if (code[i] === '"' || code[i] === "'") {
|
|
79
|
+
const quote = code[i];
|
|
80
|
+
const start = i;
|
|
81
|
+
i++;
|
|
82
|
+
while (i < len) {
|
|
83
|
+
if (code[i] === '\\') {
|
|
84
|
+
i += 2;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (code[i] === quote) {
|
|
88
|
+
i++;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
result += code.slice(start, i);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check for regex (simple heuristic)
|
|
98
|
+
if (code[i] === '/' && i > 0) {
|
|
99
|
+
const prev = code[i - 1];
|
|
100
|
+
// Regex can follow: ( = : [ ! & | ? { } ; , \n
|
|
101
|
+
if ('(=:[!&|?{};,\n'.includes(prev) || /\s/.test(prev)) {
|
|
102
|
+
const start = i;
|
|
103
|
+
i++;
|
|
104
|
+
while (i < len) {
|
|
105
|
+
if (code[i] === '\\') {
|
|
106
|
+
i += 2;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (code[i] === '/') {
|
|
110
|
+
i++;
|
|
111
|
+
// Skip flags
|
|
112
|
+
while (i < len && /[gimsuy]/.test(code[i])) i++;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
if (code[i] === '\n') break; // Invalid regex
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
118
|
+
result += code.slice(start, i);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check for const/let keywords
|
|
124
|
+
if (isWordBoundary(code, i)) {
|
|
125
|
+
if (code.slice(i, i + 5) === 'const' && isWordBoundary(code, i + 5)) {
|
|
126
|
+
result += 'var';
|
|
127
|
+
i += 5;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (code.slice(i, i + 3) === 'let' && isWordBoundary(code, i + 3)) {
|
|
131
|
+
result += 'var';
|
|
132
|
+
i += 3;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
result += code[i];
|
|
138
|
+
i++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Check if position is at a word boundary
|
|
146
|
+
* @param {string} code
|
|
147
|
+
* @param {number} pos
|
|
148
|
+
* @returns {boolean}
|
|
149
|
+
*/
|
|
150
|
+
function isWordBoundary(code, pos) {
|
|
151
|
+
if (pos === 0) return true;
|
|
152
|
+
if (pos >= code.length) return true;
|
|
153
|
+
|
|
154
|
+
const before = code[pos - 1];
|
|
155
|
+
const after = code[pos];
|
|
156
|
+
|
|
157
|
+
const isWordChar = (c) => /[a-zA-Z0-9_$]/.test(c);
|
|
158
|
+
|
|
159
|
+
// Boundary if previous char is not a word char
|
|
160
|
+
if (pos > 0 && isWordChar(before)) return false;
|
|
161
|
+
// Or if position is at end and next char is not word char
|
|
162
|
+
if (pos < code.length && !isWordChar(after)) return true;
|
|
163
|
+
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if position after keyword is a word boundary
|
|
169
|
+
* @param {string} code
|
|
170
|
+
* @param {number} pos - Position after the keyword
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
173
|
+
function isWordBoundaryAfter(code, pos) {
|
|
174
|
+
if (pos >= code.length) return true;
|
|
175
|
+
return !/[a-zA-Z0-9_$]/.test(code[pos]);
|
|
176
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analysis Types
|
|
3
|
+
*
|
|
4
|
+
* Types for code analysis (MRP /is_complete and /format endpoints).
|
|
5
|
+
* @module types/analysis
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {'complete' | 'incomplete' | 'invalid' | 'unknown'} CompletenessStatus
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} IsCompleteResult
|
|
14
|
+
* @property {CompletenessStatus} status - Completeness status
|
|
15
|
+
* @property {string} [indent] - Suggested indent for continuation
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {Object} FormatResult
|
|
20
|
+
* @property {string} formatted - Formatted code
|
|
21
|
+
* @property {boolean} changed - Whether code was changed
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capabilities Types
|
|
3
|
+
*
|
|
4
|
+
* Types for runtime capability discovery (MRP /capabilities endpoint).
|
|
5
|
+
* @module types/capabilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} Capabilities
|
|
10
|
+
* @property {string} runtime - Runtime identifier
|
|
11
|
+
* @property {string} version - Runtime version
|
|
12
|
+
* @property {string[]} languages - Supported language identifiers
|
|
13
|
+
* @property {Features} features - Feature support flags
|
|
14
|
+
* @property {string} [lspFallback] - LSP fallback WebSocket URL
|
|
15
|
+
* @property {string} defaultSession - Default session ID
|
|
16
|
+
* @property {number} maxSessions - Maximum concurrent sessions
|
|
17
|
+
* @property {BrowserEnvironment} environment - Environment information
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} Features
|
|
22
|
+
* @property {boolean} execute - Execute code and return result
|
|
23
|
+
* @property {boolean} executeStream - Stream execution output
|
|
24
|
+
* @property {boolean} interrupt - Interrupt running execution
|
|
25
|
+
* @property {boolean} complete - Tab completion from live session
|
|
26
|
+
* @property {boolean} inspect - Get symbol info (signature, docs, source)
|
|
27
|
+
* @property {boolean} hover - Quick value/type preview
|
|
28
|
+
* @property {boolean} variables - List variables in namespace
|
|
29
|
+
* @property {boolean} variableExpand - Drill into objects
|
|
30
|
+
* @property {boolean} reset - Clear namespace without destroying session
|
|
31
|
+
* @property {boolean} isComplete - Check if code is complete statement
|
|
32
|
+
* @property {boolean} format - Format/prettify code
|
|
33
|
+
* @property {boolean} assets - Asset support (blob URLs)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} BrowserEnvironment
|
|
38
|
+
* @property {string} userAgent - User agent string
|
|
39
|
+
* @property {string} language - Browser language
|
|
40
|
+
* @property {string} platform - Platform
|
|
41
|
+
* @property {boolean} isSecureContext - Is secure context (HTTPS)
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completion Types
|
|
3
|
+
*
|
|
4
|
+
* Types for code completion (MRP /complete endpoint).
|
|
5
|
+
* @module types/completion
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {'invoked' | 'character' | 'incomplete'} TriggerKind
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} CompleteOptions
|
|
14
|
+
* @property {string} [session] - Session ID
|
|
15
|
+
* @property {TriggerKind} [triggerKind='invoked'] - What triggered completion
|
|
16
|
+
* @property {string} [triggerCharacter] - Character that triggered
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {'runtime' | 'lsp' | 'static'} CompletionSource
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} CompletionResult
|
|
25
|
+
* @property {CompletionItem[]} matches - Completion items
|
|
26
|
+
* @property {number} cursorStart - Start of text to replace
|
|
27
|
+
* @property {number} cursorEnd - End of text to replace
|
|
28
|
+
* @property {CompletionSource} source - Where completions came from
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {'variable' | 'function' | 'method' | 'property' | 'class' | 'module' | 'keyword' | 'constant' | 'field' | 'value' | 'snippet'} CompletionKind
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {Object} CompletionItem
|
|
37
|
+
* @property {string} label - Display label
|
|
38
|
+
* @property {string} [insertText] - Text to insert (if different from label)
|
|
39
|
+
* @property {CompletionKind} kind - Item kind for icon
|
|
40
|
+
* @property {string} [detail] - Short description
|
|
41
|
+
* @property {string} [documentation] - Documentation (markdown)
|
|
42
|
+
* @property {string} [valuePreview] - Live value preview (from runtime)
|
|
43
|
+
* @property {string} [type] - Type string
|
|
44
|
+
* @property {number} [sortPriority] - Sort priority (lower = higher)
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
export {};
|