stone-lang 0.1.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 +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- package/packages/semver.js +635 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Liveness Analyzer for Stone
|
|
3
|
+
*
|
|
4
|
+
* Performs backward dataflow analysis to determine the last use of each variable.
|
|
5
|
+
* Stone's semantics enable precise analysis:
|
|
6
|
+
* - Immutable bindings → values never change after definition
|
|
7
|
+
* - No shadowing → each name = one value in scope
|
|
8
|
+
* - Whole-program analysis → we can see all uses at compile time
|
|
9
|
+
*
|
|
10
|
+
* Output is backend-agnostic, reusable for:
|
|
11
|
+
* - JS VM: CLEAR_LOCAL opcode emission
|
|
12
|
+
* - WASM codegen: Linear memory management
|
|
13
|
+
* - C codegen: Stack allocation decisions
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* const analyzer = new LivenessAnalyzer();
|
|
17
|
+
* const info = analyzer.analyze(ast);
|
|
18
|
+
* // info.bindings: Map of name → binding metadata
|
|
19
|
+
* // info.isLastUse(name, node): Check if node is the last use of variable
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {Object} BindingInfo
|
|
24
|
+
* @property {string} name - Variable name
|
|
25
|
+
* @property {string} scope - Scope identifier (e.g., "function:main", "block:1")
|
|
26
|
+
* @property {Object} definedAt - Location where the binding is defined
|
|
27
|
+
* @property {Object|null} lastUseAt - Location of the last use (null if never used)
|
|
28
|
+
* @property {Array} uses - Array of all use locations
|
|
29
|
+
* @property {boolean} escapes - Whether the variable escapes via closure
|
|
30
|
+
* @property {boolean} canStackAlloc - Whether safe for stack allocation
|
|
31
|
+
* @property {string} type - Type of value if known
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef {Object} LivenessInfo
|
|
36
|
+
* @property {Map<string, BindingInfo>} bindings - Map of binding key to binding info
|
|
37
|
+
* @property {function(string, Object): boolean} isLastUse - Check if a node is the last use
|
|
38
|
+
* @property {function(Object): string|null} getLastUseName - Get variable name if this is its last use
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export class LivenessAnalyzer {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.bindings = new Map(); // key -> BindingInfo
|
|
44
|
+
this.currentScope = [];
|
|
45
|
+
this.scopeCounter = 0;
|
|
46
|
+
this.lastUseMap = new Map(); // nodeId -> { name, scopeKey }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Generate a unique key for a binding
|
|
51
|
+
* @param {string} name - Variable name
|
|
52
|
+
* @param {string} scope - Scope identifier
|
|
53
|
+
* @returns {string} Unique key
|
|
54
|
+
*/
|
|
55
|
+
_bindingKey(name, scope) {
|
|
56
|
+
return `${scope}:${name}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get current scope identifier
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
_currentScopeId() {
|
|
64
|
+
return this.currentScope.length > 0
|
|
65
|
+
? this.currentScope[this.currentScope.length - 1]
|
|
66
|
+
: 'global';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate a node ID for tracking last uses
|
|
71
|
+
* @param {Object} node - AST node
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
_nodeId(node) {
|
|
75
|
+
if (!node) return 'unknown';
|
|
76
|
+
const loc = node.location || node.loc || {};
|
|
77
|
+
return `${loc.start?.line || 0}:${loc.start?.column || 0}:${node.type || 'node'}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Enter a new scope
|
|
82
|
+
* @param {string} type - Scope type (function, block, loop, etc.)
|
|
83
|
+
* @param {string} name - Optional scope name
|
|
84
|
+
*/
|
|
85
|
+
_enterScope(type, name = '') {
|
|
86
|
+
const scopeId = `${type}:${name || this.scopeCounter++}`;
|
|
87
|
+
this.currentScope.push(scopeId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Exit current scope
|
|
92
|
+
*/
|
|
93
|
+
_exitScope() {
|
|
94
|
+
this.currentScope.pop();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Record a binding definition
|
|
99
|
+
* @param {string} name - Variable name
|
|
100
|
+
* @param {Object} node - AST node where it's defined
|
|
101
|
+
* @param {string} type - Optional type annotation
|
|
102
|
+
*/
|
|
103
|
+
_recordDefinition(name, node, type = 'any') {
|
|
104
|
+
const scope = this._currentScopeId();
|
|
105
|
+
const key = this._bindingKey(name, scope);
|
|
106
|
+
|
|
107
|
+
const loc = node.location || node.loc || {};
|
|
108
|
+
this.bindings.set(key, {
|
|
109
|
+
name,
|
|
110
|
+
scope,
|
|
111
|
+
definedAt: {
|
|
112
|
+
line: loc.start?.line || 0,
|
|
113
|
+
column: loc.start?.column || 0,
|
|
114
|
+
},
|
|
115
|
+
lastUseAt: null,
|
|
116
|
+
uses: [],
|
|
117
|
+
escapes: false,
|
|
118
|
+
canStackAlloc: true,
|
|
119
|
+
type,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Record a variable use
|
|
125
|
+
* @param {string} name - Variable name
|
|
126
|
+
* @param {Object} node - AST node where it's used
|
|
127
|
+
*/
|
|
128
|
+
_recordUse(name, node) {
|
|
129
|
+
// Find the binding in current or enclosing scopes
|
|
130
|
+
let key = null;
|
|
131
|
+
for (let i = this.currentScope.length - 1; i >= 0; i--) {
|
|
132
|
+
const tryKey = this._bindingKey(name, this.currentScope[i]);
|
|
133
|
+
if (this.bindings.has(tryKey)) {
|
|
134
|
+
key = tryKey;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check global scope
|
|
140
|
+
if (!key) {
|
|
141
|
+
const globalKey = this._bindingKey(name, 'global');
|
|
142
|
+
if (this.bindings.has(globalKey)) {
|
|
143
|
+
key = globalKey;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!key) return; // Unknown variable (might be builtin or external)
|
|
148
|
+
|
|
149
|
+
const binding = this.bindings.get(key);
|
|
150
|
+
const loc = node.location || node.loc || {};
|
|
151
|
+
const useInfo = {
|
|
152
|
+
line: loc.start?.line || 0,
|
|
153
|
+
column: loc.start?.column || 0,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
binding.uses.push(useInfo);
|
|
157
|
+
binding.lastUseAt = useInfo; // Will be overwritten by later uses
|
|
158
|
+
|
|
159
|
+
// Track for isLastUse lookup
|
|
160
|
+
const nodeId = this._nodeId(node);
|
|
161
|
+
this.lastUseMap.set(nodeId, { name, scopeKey: key, binding });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Mark a binding as escaping (captured by closure)
|
|
166
|
+
* @param {string} name - Variable name
|
|
167
|
+
*/
|
|
168
|
+
_markEscapes(name) {
|
|
169
|
+
// Find the binding in enclosing scopes
|
|
170
|
+
for (let i = this.currentScope.length - 1; i >= 0; i--) {
|
|
171
|
+
const key = this._bindingKey(name, this.currentScope[i]);
|
|
172
|
+
if (this.bindings.has(key)) {
|
|
173
|
+
const binding = this.bindings.get(key);
|
|
174
|
+
binding.escapes = true;
|
|
175
|
+
binding.canStackAlloc = false;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check global scope
|
|
181
|
+
const globalKey = this._bindingKey(name, 'global');
|
|
182
|
+
if (this.bindings.has(globalKey)) {
|
|
183
|
+
const binding = this.bindings.get(globalKey);
|
|
184
|
+
binding.escapes = true;
|
|
185
|
+
binding.canStackAlloc = false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Analyze an AST and return liveness information
|
|
191
|
+
* @param {Object} ast - The AST to analyze
|
|
192
|
+
* @returns {LivenessInfo} Liveness information
|
|
193
|
+
*/
|
|
194
|
+
analyze(ast) {
|
|
195
|
+
// Reset state
|
|
196
|
+
this.bindings.clear();
|
|
197
|
+
this.currentScope = [];
|
|
198
|
+
this.scopeCounter = 0;
|
|
199
|
+
this.lastUseMap.clear();
|
|
200
|
+
|
|
201
|
+
// Phase 1: Collect all definitions and uses
|
|
202
|
+
this._collectDefinitionsAndUses(ast);
|
|
203
|
+
|
|
204
|
+
// Phase 2: Finalize last-use information
|
|
205
|
+
// The lastUseMap now contains all uses, but we need to keep only the actual last uses
|
|
206
|
+
const finalLastUseMap = new Map();
|
|
207
|
+
for (const binding of this.bindings.values()) {
|
|
208
|
+
if (binding.uses.length > 0) {
|
|
209
|
+
// The last use is the one with the highest line/column
|
|
210
|
+
const lastUse = binding.uses[binding.uses.length - 1];
|
|
211
|
+
binding.lastUseAt = lastUse;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Update lastUseMap to only include actual last uses
|
|
216
|
+
for (const [nodeId, info] of this.lastUseMap) {
|
|
217
|
+
const binding = info.binding;
|
|
218
|
+
if (binding && binding.uses.length > 0) {
|
|
219
|
+
const lastUse = binding.lastUseAt;
|
|
220
|
+
const nodeUse = this._nodeIdToLocation(nodeId);
|
|
221
|
+
if (nodeUse.line === lastUse.line && nodeUse.column === lastUse.column) {
|
|
222
|
+
finalLastUseMap.set(nodeId, info);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
this.lastUseMap = finalLastUseMap;
|
|
227
|
+
|
|
228
|
+
// Create the result object
|
|
229
|
+
const bindings = new Map(this.bindings);
|
|
230
|
+
const lastUseMap = new Map(this.lastUseMap);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
bindings,
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Check if a node is the last use of a variable
|
|
237
|
+
* @param {string} name - Variable name
|
|
238
|
+
* @param {Object} node - AST node to check
|
|
239
|
+
* @returns {boolean}
|
|
240
|
+
*/
|
|
241
|
+
isLastUse: (name, node) => {
|
|
242
|
+
const nodeId = this._nodeId(node);
|
|
243
|
+
const info = lastUseMap.get(nodeId);
|
|
244
|
+
if (!info) return false;
|
|
245
|
+
if (info.name !== name) return false;
|
|
246
|
+
|
|
247
|
+
// Don't clear if the variable escapes
|
|
248
|
+
const binding = info.binding;
|
|
249
|
+
if (binding && binding.escapes) return false;
|
|
250
|
+
|
|
251
|
+
return true;
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get the variable name if this node is its last use
|
|
256
|
+
* @param {Object} node - AST node to check
|
|
257
|
+
* @returns {string|null} Variable name or null
|
|
258
|
+
*/
|
|
259
|
+
getLastUseName: (node) => {
|
|
260
|
+
const nodeId = this._nodeId(node);
|
|
261
|
+
const info = lastUseMap.get(nodeId);
|
|
262
|
+
if (!info) return null;
|
|
263
|
+
|
|
264
|
+
// Don't clear if the variable escapes
|
|
265
|
+
const binding = info.binding;
|
|
266
|
+
if (binding && binding.escapes) return null;
|
|
267
|
+
|
|
268
|
+
return info.name;
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Parse a node ID back to a location
|
|
275
|
+
* @param {string} nodeId
|
|
276
|
+
* @returns {{line: number, column: number}}
|
|
277
|
+
*/
|
|
278
|
+
_nodeIdToLocation(nodeId) {
|
|
279
|
+
const parts = nodeId.split(':');
|
|
280
|
+
return {
|
|
281
|
+
line: parseInt(parts[0], 10) || 0,
|
|
282
|
+
column: parseInt(parts[1], 10) || 0,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Traverse the AST and collect all definitions and uses
|
|
288
|
+
* @param {Object} node - AST node
|
|
289
|
+
*/
|
|
290
|
+
_collectDefinitionsAndUses(node) {
|
|
291
|
+
if (!node || typeof node !== 'object') return;
|
|
292
|
+
|
|
293
|
+
switch (node.type) {
|
|
294
|
+
case 'Program':
|
|
295
|
+
this._enterScope('global', 'main');
|
|
296
|
+
if (node.body) {
|
|
297
|
+
for (const stmt of node.body) {
|
|
298
|
+
this._collectDefinitionsAndUses(stmt);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
this._exitScope();
|
|
302
|
+
break;
|
|
303
|
+
|
|
304
|
+
case 'BindingStatement':
|
|
305
|
+
case 'Declaration':
|
|
306
|
+
// Record the definition
|
|
307
|
+
if (node.name) {
|
|
308
|
+
const typeStr = node.typeAnnotation?.name || 'any';
|
|
309
|
+
this._recordDefinition(node.name, node, typeStr);
|
|
310
|
+
}
|
|
311
|
+
// Analyze the value expression
|
|
312
|
+
if (node.value) {
|
|
313
|
+
this._collectDefinitionsAndUses(node.value);
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case 'FunctionDeclaration':
|
|
318
|
+
case 'FunctionExpression':
|
|
319
|
+
case 'ArrowFunction':
|
|
320
|
+
// Record function name if it exists
|
|
321
|
+
if (node.name) {
|
|
322
|
+
this._recordDefinition(node.name, node, 'function');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Enter function scope
|
|
326
|
+
this._enterScope('function', node.name || '');
|
|
327
|
+
|
|
328
|
+
// Record parameters as definitions
|
|
329
|
+
if (node.params) {
|
|
330
|
+
for (const param of node.params) {
|
|
331
|
+
const paramName = typeof param === 'string' ? param : param.name;
|
|
332
|
+
if (paramName) {
|
|
333
|
+
const typeStr = param.typeAnnotation?.name || 'any';
|
|
334
|
+
this._recordDefinition(paramName, param.node || node, typeStr);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Check for captured variables (closures)
|
|
340
|
+
this._checkForClosures(node.body);
|
|
341
|
+
|
|
342
|
+
// Analyze function body
|
|
343
|
+
if (node.body) {
|
|
344
|
+
this._collectDefinitionsAndUses(node.body);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
this._exitScope();
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case 'BlockExpression':
|
|
351
|
+
case 'Block':
|
|
352
|
+
this._enterScope('block');
|
|
353
|
+
if (node.body) {
|
|
354
|
+
for (const stmt of node.body) {
|
|
355
|
+
this._collectDefinitionsAndUses(stmt);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (node.result) {
|
|
359
|
+
this._collectDefinitionsAndUses(node.result);
|
|
360
|
+
}
|
|
361
|
+
this._exitScope();
|
|
362
|
+
break;
|
|
363
|
+
|
|
364
|
+
case 'IfExpression':
|
|
365
|
+
case 'ConditionalExpression':
|
|
366
|
+
this._collectDefinitionsAndUses(node.condition || node.test);
|
|
367
|
+
this._collectDefinitionsAndUses(node.consequent || node.then);
|
|
368
|
+
this._collectDefinitionsAndUses(node.alternate || node.else);
|
|
369
|
+
break;
|
|
370
|
+
|
|
371
|
+
case 'ForExpression':
|
|
372
|
+
case 'ForLoop':
|
|
373
|
+
this._enterScope('loop');
|
|
374
|
+
// Loop variable
|
|
375
|
+
if (node.variable) {
|
|
376
|
+
this._recordDefinition(node.variable, node, 'num');
|
|
377
|
+
}
|
|
378
|
+
// Initial state
|
|
379
|
+
if (node.init) {
|
|
380
|
+
this._collectDefinitionsAndUses(node.init);
|
|
381
|
+
}
|
|
382
|
+
// Iterable
|
|
383
|
+
if (node.iterable) {
|
|
384
|
+
this._collectDefinitionsAndUses(node.iterable);
|
|
385
|
+
}
|
|
386
|
+
// Body
|
|
387
|
+
if (node.body) {
|
|
388
|
+
this._collectDefinitionsAndUses(node.body);
|
|
389
|
+
}
|
|
390
|
+
this._exitScope();
|
|
391
|
+
break;
|
|
392
|
+
|
|
393
|
+
case 'WhileLoop':
|
|
394
|
+
this._enterScope('loop');
|
|
395
|
+
if (node.condition) {
|
|
396
|
+
this._collectDefinitionsAndUses(node.condition);
|
|
397
|
+
}
|
|
398
|
+
if (node.body) {
|
|
399
|
+
this._collectDefinitionsAndUses(node.body);
|
|
400
|
+
}
|
|
401
|
+
this._exitScope();
|
|
402
|
+
break;
|
|
403
|
+
|
|
404
|
+
case 'Identifier':
|
|
405
|
+
// This is a variable use
|
|
406
|
+
if (node.name) {
|
|
407
|
+
this._recordUse(node.name, node);
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
|
|
411
|
+
case 'CallExpression':
|
|
412
|
+
case 'Call':
|
|
413
|
+
// Analyze callee and arguments
|
|
414
|
+
this._collectDefinitionsAndUses(node.callee || node.func);
|
|
415
|
+
if (node.arguments || node.args) {
|
|
416
|
+
for (const arg of node.arguments || node.args) {
|
|
417
|
+
this._collectDefinitionsAndUses(arg);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
break;
|
|
421
|
+
|
|
422
|
+
case 'BinaryExpression':
|
|
423
|
+
case 'BinaryOp':
|
|
424
|
+
this._collectDefinitionsAndUses(node.left);
|
|
425
|
+
this._collectDefinitionsAndUses(node.right);
|
|
426
|
+
break;
|
|
427
|
+
|
|
428
|
+
case 'UnaryExpression':
|
|
429
|
+
case 'UnaryOp':
|
|
430
|
+
this._collectDefinitionsAndUses(node.argument || node.operand);
|
|
431
|
+
break;
|
|
432
|
+
|
|
433
|
+
case 'MemberExpression':
|
|
434
|
+
case 'PropertyAccess':
|
|
435
|
+
this._collectDefinitionsAndUses(node.object);
|
|
436
|
+
// Don't record property name as a use
|
|
437
|
+
break;
|
|
438
|
+
|
|
439
|
+
case 'IndexExpression':
|
|
440
|
+
case 'IndexAccess':
|
|
441
|
+
this._collectDefinitionsAndUses(node.object || node.array);
|
|
442
|
+
this._collectDefinitionsAndUses(node.index);
|
|
443
|
+
break;
|
|
444
|
+
|
|
445
|
+
case 'ArrayLiteral':
|
|
446
|
+
case 'ArrayExpression':
|
|
447
|
+
if (node.elements) {
|
|
448
|
+
for (const elem of node.elements) {
|
|
449
|
+
this._collectDefinitionsAndUses(elem);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
|
|
454
|
+
case 'ObjectLiteral':
|
|
455
|
+
case 'ObjectExpression':
|
|
456
|
+
case 'RecordLiteral':
|
|
457
|
+
if (node.properties || node.fields) {
|
|
458
|
+
for (const prop of node.properties || node.fields) {
|
|
459
|
+
// Only analyze value, not key
|
|
460
|
+
this._collectDefinitionsAndUses(prop.value);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
break;
|
|
464
|
+
|
|
465
|
+
case 'ReturnStatement':
|
|
466
|
+
if (node.value) {
|
|
467
|
+
this._collectDefinitionsAndUses(node.value);
|
|
468
|
+
}
|
|
469
|
+
break;
|
|
470
|
+
|
|
471
|
+
case 'ExpressionStatement':
|
|
472
|
+
this._collectDefinitionsAndUses(node.expression);
|
|
473
|
+
break;
|
|
474
|
+
|
|
475
|
+
default:
|
|
476
|
+
// Generic traversal for unknown node types
|
|
477
|
+
for (const key of Object.keys(node)) {
|
|
478
|
+
if (key === 'location' || key === 'loc' || key === 'type') continue;
|
|
479
|
+
const value = node[key];
|
|
480
|
+
if (Array.isArray(value)) {
|
|
481
|
+
for (const item of value) {
|
|
482
|
+
if (item && typeof item === 'object') {
|
|
483
|
+
this._collectDefinitionsAndUses(item);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
} else if (value && typeof value === 'object') {
|
|
487
|
+
this._collectDefinitionsAndUses(value);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Check for variables that are captured by closures
|
|
495
|
+
* @param {Object} node - AST node to check
|
|
496
|
+
*/
|
|
497
|
+
_checkForClosures(node) {
|
|
498
|
+
if (!node || typeof node !== 'object') return;
|
|
499
|
+
|
|
500
|
+
// If we find a function inside, check for captured variables
|
|
501
|
+
if (node.type === 'FunctionExpression' ||
|
|
502
|
+
node.type === 'FunctionDeclaration' ||
|
|
503
|
+
node.type === 'ArrowFunction') {
|
|
504
|
+
// Collect all identifiers used in this function
|
|
505
|
+
const usedIds = this._collectIdentifiers(node.body);
|
|
506
|
+
|
|
507
|
+
// Mark any that are from outer scopes as escaping
|
|
508
|
+
for (const name of usedIds) {
|
|
509
|
+
// Check if it's defined in an outer scope (not in this function's params)
|
|
510
|
+
const isParam = node.params?.some(p =>
|
|
511
|
+
(typeof p === 'string' ? p : p.name) === name
|
|
512
|
+
);
|
|
513
|
+
if (!isParam) {
|
|
514
|
+
this._markEscapes(name);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Continue checking child nodes
|
|
520
|
+
for (const key of Object.keys(node)) {
|
|
521
|
+
if (key === 'location' || key === 'loc' || key === 'type') continue;
|
|
522
|
+
const value = node[key];
|
|
523
|
+
if (Array.isArray(value)) {
|
|
524
|
+
for (const item of value) {
|
|
525
|
+
if (item && typeof item === 'object') {
|
|
526
|
+
this._checkForClosures(item);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} else if (value && typeof value === 'object') {
|
|
530
|
+
this._checkForClosures(value);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Collect all identifier names used in a subtree
|
|
537
|
+
* @param {Object} node - AST node
|
|
538
|
+
* @returns {Set<string>} Set of identifier names
|
|
539
|
+
*/
|
|
540
|
+
_collectIdentifiers(node) {
|
|
541
|
+
const identifiers = new Set();
|
|
542
|
+
|
|
543
|
+
const visit = (n) => {
|
|
544
|
+
if (!n || typeof n !== 'object') return;
|
|
545
|
+
|
|
546
|
+
if (n.type === 'Identifier' && n.name) {
|
|
547
|
+
identifiers.add(n.name);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
for (const key of Object.keys(n)) {
|
|
551
|
+
if (key === 'location' || key === 'loc' || key === 'type') continue;
|
|
552
|
+
const value = n[key];
|
|
553
|
+
if (Array.isArray(value)) {
|
|
554
|
+
for (const item of value) {
|
|
555
|
+
visit(item);
|
|
556
|
+
}
|
|
557
|
+
} else if (value && typeof value === 'object') {
|
|
558
|
+
visit(value);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
visit(node);
|
|
564
|
+
return identifiers;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export default LivenessAnalyzer;
|