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
package/src/lsp/hover.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hover Information
|
|
3
|
+
*
|
|
4
|
+
* Provides hover information (type and value preview) for symbols
|
|
5
|
+
* by introspecting live values in the execution context.
|
|
6
|
+
*
|
|
7
|
+
* @module lsp/hover
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { parseIdentifierAtPosition, splitObjectPath } from './parse.js';
|
|
11
|
+
import { formatValueShort, getTypeName, getFunctionSignature } from './format.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {import('../session/context/interface.js').ExecutionContext} ExecutionContext
|
|
15
|
+
* @typedef {import('../types/inspection.js').HoverResult} HoverResult
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get hover information at cursor position
|
|
20
|
+
*
|
|
21
|
+
* @param {string} code - The code being edited
|
|
22
|
+
* @param {number} cursor - Cursor position (0-indexed)
|
|
23
|
+
* @param {ExecutionContext} context - Execution context for live values
|
|
24
|
+
* @returns {HoverResult}
|
|
25
|
+
*/
|
|
26
|
+
export function getHoverInfo(code, cursor, context) {
|
|
27
|
+
// Find identifier at cursor
|
|
28
|
+
const identifier = parseIdentifierAtPosition(code, cursor);
|
|
29
|
+
|
|
30
|
+
if (!identifier) {
|
|
31
|
+
return { found: false };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Resolve the value
|
|
35
|
+
const value = resolveValue(identifier.full, context);
|
|
36
|
+
|
|
37
|
+
// Check if it exists
|
|
38
|
+
const exists = value !== undefined || hasVariable(identifier.full, context);
|
|
39
|
+
|
|
40
|
+
if (!exists) {
|
|
41
|
+
return { found: false };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @type {HoverResult} */
|
|
45
|
+
const result = {
|
|
46
|
+
found: true,
|
|
47
|
+
name: identifier.full,
|
|
48
|
+
type: getTypeName(value),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Add signature for functions
|
|
52
|
+
if (typeof value === 'function') {
|
|
53
|
+
result.signature = getFunctionSignature(value);
|
|
54
|
+
} else {
|
|
55
|
+
// Add value preview for non-functions
|
|
56
|
+
result.value = formatValueShort(value, 100);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve a value from an object path in the context
|
|
64
|
+
* @param {string} path
|
|
65
|
+
* @param {ExecutionContext} context
|
|
66
|
+
* @returns {*}
|
|
67
|
+
*/
|
|
68
|
+
function resolveValue(path, context) {
|
|
69
|
+
const parts = splitObjectPath(path);
|
|
70
|
+
if (parts.length === 0) return undefined;
|
|
71
|
+
|
|
72
|
+
// Start with user variables or global
|
|
73
|
+
let value = context.getVariable(parts[0]);
|
|
74
|
+
|
|
75
|
+
if (value === undefined) {
|
|
76
|
+
// Try global
|
|
77
|
+
const global = context.getGlobal();
|
|
78
|
+
if (global && parts[0] in global) {
|
|
79
|
+
// @ts-ignore
|
|
80
|
+
value = global[parts[0]];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (value === undefined) return undefined;
|
|
85
|
+
|
|
86
|
+
// Navigate path
|
|
87
|
+
for (let i = 1; i < parts.length; i++) {
|
|
88
|
+
if (value === null || value === undefined) return undefined;
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
if (value instanceof Map) {
|
|
92
|
+
value = value.get(parts[i]);
|
|
93
|
+
} else {
|
|
94
|
+
// @ts-ignore
|
|
95
|
+
value = value[parts[i]];
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a variable exists in context
|
|
107
|
+
* @param {string} path
|
|
108
|
+
* @param {ExecutionContext} context
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
function hasVariable(path, context) {
|
|
112
|
+
const parts = splitObjectPath(path);
|
|
113
|
+
if (parts.length === 0) return false;
|
|
114
|
+
|
|
115
|
+
if (context.hasVariable(parts[0])) {
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check global
|
|
120
|
+
const global = context.getGlobal();
|
|
121
|
+
if (global && parts[0] in global) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return false;
|
|
126
|
+
}
|
package/src/lsp/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Features
|
|
3
|
+
*
|
|
4
|
+
* Language Server Protocol-like features for JavaScript runtime.
|
|
5
|
+
* These provide completions, hover, inspection, and variable listing
|
|
6
|
+
* based on live runtime values.
|
|
7
|
+
*
|
|
8
|
+
* @module lsp
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Parsing utilities
|
|
12
|
+
export {
|
|
13
|
+
parseIdentifierAtPosition,
|
|
14
|
+
parseCompletionContext,
|
|
15
|
+
getStringOrCommentContext,
|
|
16
|
+
getWordAtCursor,
|
|
17
|
+
splitObjectPath,
|
|
18
|
+
isIdentifierStart,
|
|
19
|
+
isIdentifierPart,
|
|
20
|
+
isKeyword,
|
|
21
|
+
getKeywords,
|
|
22
|
+
getCommonGlobals,
|
|
23
|
+
} from './parse.js';
|
|
24
|
+
|
|
25
|
+
// Value formatting
|
|
26
|
+
export {
|
|
27
|
+
formatValue,
|
|
28
|
+
formatValueShort,
|
|
29
|
+
getTypeName,
|
|
30
|
+
getCompletionKind,
|
|
31
|
+
isExpandable,
|
|
32
|
+
getFunctionSignature,
|
|
33
|
+
getFunctionSource,
|
|
34
|
+
getSizeDescription,
|
|
35
|
+
} from './format.js';
|
|
36
|
+
|
|
37
|
+
// Completions
|
|
38
|
+
export { getCompletions } from './complete.js';
|
|
39
|
+
|
|
40
|
+
// Hover
|
|
41
|
+
export { getHoverInfo } from './hover.js';
|
|
42
|
+
|
|
43
|
+
// Inspection
|
|
44
|
+
export { getInspectInfo, inspectPath } from './inspect.js';
|
|
45
|
+
|
|
46
|
+
// Variables
|
|
47
|
+
export {
|
|
48
|
+
listVariables,
|
|
49
|
+
getVariableDetail,
|
|
50
|
+
expandVariable,
|
|
51
|
+
formatVariableInfo,
|
|
52
|
+
getChildren,
|
|
53
|
+
getMethods,
|
|
54
|
+
getAttributes,
|
|
55
|
+
} from './variables.js';
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symbol Inspection
|
|
3
|
+
*
|
|
4
|
+
* Provides detailed inspection information for symbols including
|
|
5
|
+
* signature, documentation, source code, and children.
|
|
6
|
+
*
|
|
7
|
+
* @module lsp/inspect
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { parseIdentifierAtPosition, splitObjectPath } from './parse.js';
|
|
11
|
+
import {
|
|
12
|
+
formatValue,
|
|
13
|
+
formatValueShort,
|
|
14
|
+
getTypeName,
|
|
15
|
+
getCompletionKind,
|
|
16
|
+
getFunctionSignature,
|
|
17
|
+
getFunctionSource,
|
|
18
|
+
isExpandable,
|
|
19
|
+
getSizeDescription,
|
|
20
|
+
} from './format.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {import('../session/context/interface.js').ExecutionContext} ExecutionContext
|
|
24
|
+
* @typedef {import('../types/inspection.js').InspectOptions} InspectOptions
|
|
25
|
+
* @typedef {import('../types/inspection.js').InspectResult} InspectResult
|
|
26
|
+
* @typedef {import('../types/variables.js').VariableInfo} VariableInfo
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get detailed inspection information at cursor position
|
|
31
|
+
*
|
|
32
|
+
* @param {string} code - The code being edited
|
|
33
|
+
* @param {number} cursor - Cursor position (0-indexed)
|
|
34
|
+
* @param {ExecutionContext} context - Execution context for live values
|
|
35
|
+
* @param {InspectOptions} [options]
|
|
36
|
+
* @returns {InspectResult}
|
|
37
|
+
*/
|
|
38
|
+
export function getInspectInfo(code, cursor, context, options = {}) {
|
|
39
|
+
const detail = options.detail ?? 0;
|
|
40
|
+
|
|
41
|
+
// Find identifier at cursor
|
|
42
|
+
const identifier = parseIdentifierAtPosition(code, cursor);
|
|
43
|
+
|
|
44
|
+
if (!identifier) {
|
|
45
|
+
return { found: false, source: 'runtime' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Resolve the value
|
|
49
|
+
const value = resolveValue(identifier.full, context);
|
|
50
|
+
|
|
51
|
+
// Check if it exists
|
|
52
|
+
const exists = value !== undefined || hasVariable(identifier.full, context);
|
|
53
|
+
|
|
54
|
+
if (!exists) {
|
|
55
|
+
return { found: false, source: 'runtime' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @type {InspectResult} */
|
|
59
|
+
const result = {
|
|
60
|
+
found: true,
|
|
61
|
+
source: 'runtime',
|
|
62
|
+
name: identifier.name,
|
|
63
|
+
kind: getInspectKind(value),
|
|
64
|
+
type: getTypeName(value),
|
|
65
|
+
value: formatValueShort(value, 200),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Add function-specific info
|
|
69
|
+
if (typeof value === 'function') {
|
|
70
|
+
result.signature = getFunctionSignature(value);
|
|
71
|
+
|
|
72
|
+
// Detail level 1: add docstring
|
|
73
|
+
if (detail >= 1) {
|
|
74
|
+
result.docstring = getDocstring(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Detail level 2: add source code
|
|
78
|
+
if (detail >= 2) {
|
|
79
|
+
result.sourceCode = getFunctionSource(value);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Add children for expandable values
|
|
84
|
+
if (detail >= 1 && isExpandable(value)) {
|
|
85
|
+
result.children = getChildren(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Inspect a specific object path
|
|
93
|
+
*
|
|
94
|
+
* @param {string} path - Object path to inspect (e.g., "obj.prop")
|
|
95
|
+
* @param {ExecutionContext} context
|
|
96
|
+
* @param {InspectOptions} [options]
|
|
97
|
+
* @returns {InspectResult}
|
|
98
|
+
*/
|
|
99
|
+
export function inspectPath(path, context, options = {}) {
|
|
100
|
+
const detail = options.detail ?? 0;
|
|
101
|
+
|
|
102
|
+
const value = resolveValue(path, context);
|
|
103
|
+
const exists = value !== undefined || hasVariable(path, context);
|
|
104
|
+
|
|
105
|
+
if (!exists) {
|
|
106
|
+
return { found: false, source: 'runtime' };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const parts = splitObjectPath(path);
|
|
110
|
+
const name = parts[parts.length - 1] || path;
|
|
111
|
+
|
|
112
|
+
/** @type {InspectResult} */
|
|
113
|
+
const result = {
|
|
114
|
+
found: true,
|
|
115
|
+
source: 'runtime',
|
|
116
|
+
name,
|
|
117
|
+
kind: getInspectKind(value),
|
|
118
|
+
type: getTypeName(value),
|
|
119
|
+
value: formatValueShort(value, 200),
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (typeof value === 'function') {
|
|
123
|
+
result.signature = getFunctionSignature(value);
|
|
124
|
+
|
|
125
|
+
if (detail >= 1) {
|
|
126
|
+
result.docstring = getDocstring(value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (detail >= 2) {
|
|
130
|
+
result.sourceCode = getFunctionSource(value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (detail >= 1 && isExpandable(value)) {
|
|
135
|
+
result.children = getChildren(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get kind string for inspection
|
|
143
|
+
* @param {*} value
|
|
144
|
+
* @returns {string}
|
|
145
|
+
*/
|
|
146
|
+
function getInspectKind(value) {
|
|
147
|
+
if (value === null) return 'null';
|
|
148
|
+
if (value === undefined) return 'undefined';
|
|
149
|
+
|
|
150
|
+
if (typeof value === 'function') {
|
|
151
|
+
const str = value.toString();
|
|
152
|
+
if (str.startsWith('class ')) return 'class';
|
|
153
|
+
if (/^(async\s+)?function\s*\*/.test(str)) return 'generator';
|
|
154
|
+
if (str.includes('=>')) return 'arrow-function';
|
|
155
|
+
return 'function';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (Array.isArray(value)) return 'array';
|
|
159
|
+
if (value instanceof Map) return 'map';
|
|
160
|
+
if (value instanceof Set) return 'set';
|
|
161
|
+
if (value instanceof Date) return 'date';
|
|
162
|
+
if (value instanceof RegExp) return 'regexp';
|
|
163
|
+
if (value instanceof Error) return 'error';
|
|
164
|
+
if (value instanceof Promise) return 'promise';
|
|
165
|
+
|
|
166
|
+
const type = typeof value;
|
|
167
|
+
if (type === 'object') return 'object';
|
|
168
|
+
|
|
169
|
+
return type;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get docstring for a function (if available)
|
|
174
|
+
* @param {Function} fn
|
|
175
|
+
* @returns {string | undefined}
|
|
176
|
+
*/
|
|
177
|
+
function getDocstring(fn) {
|
|
178
|
+
if (typeof fn !== 'function') return undefined;
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const source = fn.toString();
|
|
182
|
+
|
|
183
|
+
// Try to find JSDoc-style comments
|
|
184
|
+
// Look for /** ... */ before function declaration
|
|
185
|
+
// This won't work for most runtime functions, but worth trying
|
|
186
|
+
const jsdocMatch = source.match(/\/\*\*([\s\S]*?)\*\//);
|
|
187
|
+
if (jsdocMatch) {
|
|
188
|
+
return jsdocMatch[1]
|
|
189
|
+
.split('\n')
|
|
190
|
+
.map(line => line.replace(/^\s*\*\s?/, '').trim())
|
|
191
|
+
.filter(line => line && !line.startsWith('@'))
|
|
192
|
+
.join('\n')
|
|
193
|
+
.trim();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check for built-in documentation (MDN-style)
|
|
197
|
+
const builtinDocs = getBuiltinDocumentation(fn);
|
|
198
|
+
if (builtinDocs) {
|
|
199
|
+
return builtinDocs;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return undefined;
|
|
203
|
+
} catch {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get documentation for built-in functions
|
|
210
|
+
* @param {Function} fn
|
|
211
|
+
* @returns {string | undefined}
|
|
212
|
+
*/
|
|
213
|
+
function getBuiltinDocumentation(fn) {
|
|
214
|
+
// Map of common built-in functions to their descriptions
|
|
215
|
+
const docs = {
|
|
216
|
+
// Array methods
|
|
217
|
+
'push': 'Adds elements to the end of an array and returns the new length.',
|
|
218
|
+
'pop': 'Removes the last element from an array and returns it.',
|
|
219
|
+
'shift': 'Removes the first element from an array and returns it.',
|
|
220
|
+
'unshift': 'Adds elements to the beginning of an array and returns the new length.',
|
|
221
|
+
'slice': 'Returns a shallow copy of a portion of an array.',
|
|
222
|
+
'splice': 'Changes the contents of an array by removing or replacing elements.',
|
|
223
|
+
'map': 'Creates a new array with the results of calling a function on every element.',
|
|
224
|
+
'filter': 'Creates a new array with all elements that pass a test.',
|
|
225
|
+
'reduce': 'Executes a reducer function on each element, resulting in a single value.',
|
|
226
|
+
'forEach': 'Executes a function once for each array element.',
|
|
227
|
+
'find': 'Returns the first element that satisfies a testing function.',
|
|
228
|
+
'findIndex': 'Returns the index of the first element that satisfies a testing function.',
|
|
229
|
+
'includes': 'Determines whether an array includes a certain value.',
|
|
230
|
+
'indexOf': 'Returns the first index at which a given element can be found.',
|
|
231
|
+
'join': 'Joins all elements of an array into a string.',
|
|
232
|
+
'sort': 'Sorts the elements of an array in place and returns the array.',
|
|
233
|
+
'reverse': 'Reverses the elements of an array in place.',
|
|
234
|
+
'concat': 'Merges two or more arrays into a new array.',
|
|
235
|
+
'flat': 'Creates a new array with all sub-array elements concatenated.',
|
|
236
|
+
'flatMap': 'Maps each element then flattens the result into a new array.',
|
|
237
|
+
|
|
238
|
+
// String methods
|
|
239
|
+
'charAt': 'Returns the character at a specified index.',
|
|
240
|
+
'charCodeAt': 'Returns the Unicode value of the character at an index.',
|
|
241
|
+
'split': 'Splits a string into an array of substrings.',
|
|
242
|
+
'substring': 'Returns a portion of the string between two indices.',
|
|
243
|
+
'substr': 'Returns a portion of the string starting from an index.',
|
|
244
|
+
'toLowerCase': 'Returns the string converted to lowercase.',
|
|
245
|
+
'toUpperCase': 'Returns the string converted to uppercase.',
|
|
246
|
+
'trim': 'Removes whitespace from both ends of a string.',
|
|
247
|
+
'trimStart': 'Removes whitespace from the beginning of a string.',
|
|
248
|
+
'trimEnd': 'Removes whitespace from the end of a string.',
|
|
249
|
+
'replace': 'Returns a new string with some or all matches replaced.',
|
|
250
|
+
'replaceAll': 'Returns a new string with all matches replaced.',
|
|
251
|
+
'match': 'Retrieves the result of matching a string against a regex.',
|
|
252
|
+
'search': 'Searches for a match between a regex and the string.',
|
|
253
|
+
'startsWith': 'Determines whether a string begins with specified characters.',
|
|
254
|
+
'endsWith': 'Determines whether a string ends with specified characters.',
|
|
255
|
+
'padStart': 'Pads the string with another string until it reaches the given length.',
|
|
256
|
+
'padEnd': 'Pads the string with another string at the end.',
|
|
257
|
+
'repeat': 'Returns a new string with copies of the original string.',
|
|
258
|
+
|
|
259
|
+
// Object methods
|
|
260
|
+
'hasOwnProperty': 'Returns a boolean indicating whether the object has the property.',
|
|
261
|
+
'toString': 'Returns a string representation of the object.',
|
|
262
|
+
'valueOf': 'Returns the primitive value of the object.',
|
|
263
|
+
|
|
264
|
+
// Global functions
|
|
265
|
+
'parseInt': 'Parses a string argument and returns an integer.',
|
|
266
|
+
'parseFloat': 'Parses a string argument and returns a floating point number.',
|
|
267
|
+
'isNaN': 'Determines whether a value is NaN.',
|
|
268
|
+
'isFinite': 'Determines whether a value is a finite number.',
|
|
269
|
+
'encodeURI': 'Encodes a URI by replacing certain characters.',
|
|
270
|
+
'decodeURI': 'Decodes a URI previously created by encodeURI.',
|
|
271
|
+
'encodeURIComponent': 'Encodes a URI component by replacing certain characters.',
|
|
272
|
+
'decodeURIComponent': 'Decodes a URI component.',
|
|
273
|
+
|
|
274
|
+
// JSON
|
|
275
|
+
'parse': 'Parses a JSON string and returns the JavaScript value.',
|
|
276
|
+
'stringify': 'Converts a JavaScript value to a JSON string.',
|
|
277
|
+
|
|
278
|
+
// Math
|
|
279
|
+
'abs': 'Returns the absolute value of a number.',
|
|
280
|
+
'ceil': 'Rounds a number up to the next largest integer.',
|
|
281
|
+
'floor': 'Rounds a number down to the largest integer.',
|
|
282
|
+
'round': 'Rounds a number to the nearest integer.',
|
|
283
|
+
'max': 'Returns the largest of zero or more numbers.',
|
|
284
|
+
'min': 'Returns the smallest of zero or more numbers.',
|
|
285
|
+
'pow': 'Returns the base raised to the exponent power.',
|
|
286
|
+
'sqrt': 'Returns the square root of a number.',
|
|
287
|
+
'random': 'Returns a random number between 0 and 1.',
|
|
288
|
+
'sin': 'Returns the sine of a number.',
|
|
289
|
+
'cos': 'Returns the cosine of a number.',
|
|
290
|
+
'tan': 'Returns the tangent of a number.',
|
|
291
|
+
'log': 'Returns the natural logarithm of a number.',
|
|
292
|
+
'exp': 'Returns e raised to the power of a number.',
|
|
293
|
+
|
|
294
|
+
// Console
|
|
295
|
+
'log': 'Outputs a message to the console.',
|
|
296
|
+
'error': 'Outputs an error message to the console.',
|
|
297
|
+
'warn': 'Outputs a warning message to the console.',
|
|
298
|
+
'info': 'Outputs an informational message to the console.',
|
|
299
|
+
'debug': 'Outputs a debug message to the console.',
|
|
300
|
+
'table': 'Displays tabular data as a table.',
|
|
301
|
+
'clear': 'Clears the console.',
|
|
302
|
+
'group': 'Creates a new inline group in the console.',
|
|
303
|
+
'groupEnd': 'Exits the current inline group in the console.',
|
|
304
|
+
'time': 'Starts a timer with a specified label.',
|
|
305
|
+
'timeEnd': 'Stops a timer and logs the elapsed time.',
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const name = fn.name;
|
|
309
|
+
return docs[name];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Get children of an expandable value as VariableInfo[]
|
|
314
|
+
* @param {*} value
|
|
315
|
+
* @param {number} [maxChildren=100]
|
|
316
|
+
* @returns {VariableInfo[]}
|
|
317
|
+
*/
|
|
318
|
+
function getChildren(value, maxChildren = 100) {
|
|
319
|
+
if (value === null || value === undefined) return [];
|
|
320
|
+
|
|
321
|
+
/** @type {VariableInfo[]} */
|
|
322
|
+
const children = [];
|
|
323
|
+
|
|
324
|
+
if (Array.isArray(value)) {
|
|
325
|
+
const items = value.slice(0, maxChildren);
|
|
326
|
+
for (let i = 0; i < items.length; i++) {
|
|
327
|
+
children.push(formatVariableInfo(String(i), items[i]));
|
|
328
|
+
}
|
|
329
|
+
} else if (value instanceof Map) {
|
|
330
|
+
let count = 0;
|
|
331
|
+
for (const [k, v] of value) {
|
|
332
|
+
if (count >= maxChildren) break;
|
|
333
|
+
children.push(formatVariableInfo(String(k), v));
|
|
334
|
+
count++;
|
|
335
|
+
}
|
|
336
|
+
} else if (value instanceof Set) {
|
|
337
|
+
let count = 0;
|
|
338
|
+
for (const v of value) {
|
|
339
|
+
if (count >= maxChildren) break;
|
|
340
|
+
children.push(formatVariableInfo(String(count), v));
|
|
341
|
+
count++;
|
|
342
|
+
}
|
|
343
|
+
} else if (typeof value === 'object') {
|
|
344
|
+
const keys = Object.keys(value).slice(0, maxChildren);
|
|
345
|
+
for (const key of keys) {
|
|
346
|
+
try {
|
|
347
|
+
children.push(formatVariableInfo(key, value[key]));
|
|
348
|
+
} catch {
|
|
349
|
+
children.push({
|
|
350
|
+
name: key,
|
|
351
|
+
type: 'unknown',
|
|
352
|
+
value: '(inaccessible)',
|
|
353
|
+
expandable: false,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return children;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Format a variable for display
|
|
364
|
+
* @param {string} name
|
|
365
|
+
* @param {*} value
|
|
366
|
+
* @returns {VariableInfo}
|
|
367
|
+
*/
|
|
368
|
+
function formatVariableInfo(name, value) {
|
|
369
|
+
/** @type {VariableInfo} */
|
|
370
|
+
const info = {
|
|
371
|
+
name,
|
|
372
|
+
type: getTypeName(value),
|
|
373
|
+
value: formatValueShort(value, 100),
|
|
374
|
+
expandable: isExpandable(value),
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// Add size info
|
|
378
|
+
const size = getSizeDescription(value);
|
|
379
|
+
if (size) {
|
|
380
|
+
info.size = size;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Add length for arrays/strings
|
|
384
|
+
if (Array.isArray(value)) {
|
|
385
|
+
info.length = value.length;
|
|
386
|
+
} else if (typeof value === 'string') {
|
|
387
|
+
info.length = value.length;
|
|
388
|
+
} else if (value instanceof Map || value instanceof Set) {
|
|
389
|
+
info.length = value.size;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Add keys preview for objects
|
|
393
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
394
|
+
if (!(value instanceof Map) && !(value instanceof Set)) {
|
|
395
|
+
info.keys = Object.keys(value).slice(0, 10);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return info;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Resolve a value from an object path in the context
|
|
404
|
+
* @param {string} path
|
|
405
|
+
* @param {ExecutionContext} context
|
|
406
|
+
* @returns {*}
|
|
407
|
+
*/
|
|
408
|
+
function resolveValue(path, context) {
|
|
409
|
+
const parts = splitObjectPath(path);
|
|
410
|
+
if (parts.length === 0) return undefined;
|
|
411
|
+
|
|
412
|
+
// Start with user variables or global
|
|
413
|
+
let value = context.getVariable(parts[0]);
|
|
414
|
+
|
|
415
|
+
if (value === undefined) {
|
|
416
|
+
// Try global
|
|
417
|
+
const global = context.getGlobal();
|
|
418
|
+
if (global && parts[0] in global) {
|
|
419
|
+
// @ts-ignore
|
|
420
|
+
value = global[parts[0]];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (value === undefined) return undefined;
|
|
425
|
+
|
|
426
|
+
// Navigate path
|
|
427
|
+
for (let i = 1; i < parts.length; i++) {
|
|
428
|
+
if (value === null || value === undefined) return undefined;
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
if (value instanceof Map) {
|
|
432
|
+
value = value.get(parts[i]);
|
|
433
|
+
} else {
|
|
434
|
+
// @ts-ignore
|
|
435
|
+
value = value[parts[i]];
|
|
436
|
+
}
|
|
437
|
+
} catch {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return value;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Check if a variable exists in context
|
|
447
|
+
* @param {string} path
|
|
448
|
+
* @param {ExecutionContext} context
|
|
449
|
+
* @returns {boolean}
|
|
450
|
+
*/
|
|
451
|
+
function hasVariable(path, context) {
|
|
452
|
+
const parts = splitObjectPath(path);
|
|
453
|
+
if (parts.length === 0) return false;
|
|
454
|
+
|
|
455
|
+
if (context.hasVariable(parts[0])) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check global
|
|
460
|
+
const global = context.getGlobal();
|
|
461
|
+
if (global && parts[0] in global) {
|
|
462
|
+
return true;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return false;
|
|
466
|
+
}
|