rip-lang 3.13.135 → 3.13.136
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 +1 -1
- package/docs/dist/rip.js +379 -548
- package/docs/dist/rip.min.js +173 -181
- package/docs/dist/rip.min.js.br +0 -0
- package/package.json +1 -1
- package/src/compiler.js +2 -245
- package/src/typecheck.js +53 -0
package/docs/dist/rip.min.js.br
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/compiler.js
CHANGED
|
@@ -663,17 +663,6 @@ export class CodeEmitter {
|
|
|
663
663
|
else body.push(stmt);
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
// Classify program-level variables for inline declarations
|
|
667
|
-
let prevInlinePending = this._inlineVarsPending;
|
|
668
|
-
let programInlineVars = new Set();
|
|
669
|
-
if (this.programVars.size > 0 && body.length > 0) {
|
|
670
|
-
let classified = this.classifyVarsForInlining(body, this.programVars);
|
|
671
|
-
programInlineVars = classified.inlineVars;
|
|
672
|
-
// '_' is always emitted as 'var _' (not 'let'), so must not be inlined
|
|
673
|
-
programInlineVars.delete('_');
|
|
674
|
-
if (programInlineVars.size > 0) this._inlineVarsPending = new Set(programInlineVars);
|
|
675
|
-
}
|
|
676
|
-
|
|
677
666
|
// Generate body first to detect needed helpers
|
|
678
667
|
let blockStmts = ['def', 'class', 'if', 'for-in', 'for-of', 'for-as', 'while', 'loop', 'switch', 'try'];
|
|
679
668
|
let stmtEntries = body.map((stmt, index) => {
|
|
@@ -700,8 +689,6 @@ export class CodeEmitter {
|
|
|
700
689
|
});
|
|
701
690
|
let statementsCode = stmtEntries.map(e => e.code).join('\n');
|
|
702
691
|
|
|
703
|
-
this._inlineVarsPending = prevInlinePending;
|
|
704
|
-
|
|
705
692
|
let needsBlank = false;
|
|
706
693
|
|
|
707
694
|
if (imports.length > 0) {
|
|
@@ -713,7 +700,7 @@ export class CodeEmitter {
|
|
|
713
700
|
let hasUnderscore = this.programVars.has('_');
|
|
714
701
|
if (hasUnderscore) this.programVars.delete('_');
|
|
715
702
|
if (this.programVars.size > 0) {
|
|
716
|
-
let vars = Array.from(this.programVars).
|
|
703
|
+
let vars = Array.from(this.programVars).sort().join(', ');
|
|
717
704
|
if (vars) {
|
|
718
705
|
if (needsBlank) code += '\n';
|
|
719
706
|
code += `let ${vars};\n`;
|
|
@@ -911,14 +898,6 @@ export class CodeEmitter {
|
|
|
911
898
|
if (ctrlOp === '||') return `(() => { const __v = ${exprCode}; if (!__v) ${ctrlCode}; return (${targetCode} = __v); })()`;
|
|
912
899
|
return `(() => { const __v = ${exprCode}; if (__v) ${ctrlCode}; return (${targetCode} = __v); })()`;
|
|
913
900
|
}
|
|
914
|
-
// Inline `let` for control flow — split into declaration + guard
|
|
915
|
-
let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
|
|
916
|
-
if (tgtName && context === 'statement' && this._inlineVarsPending?.delete(tgtName)) {
|
|
917
|
-
let ind = this.indent();
|
|
918
|
-
if (ctrlOp === '??') return `let ${targetCode} = ${exprCode};\n${ind}if (${targetCode} == null) ${ctrlCode}`;
|
|
919
|
-
if (ctrlOp === '||') return `let ${targetCode} = ${exprCode};\n${ind}if (!${targetCode}) ${ctrlCode}`;
|
|
920
|
-
return `let ${targetCode} = ${exprCode};\n${ind}if (${targetCode}) ${ctrlCode}`;
|
|
921
|
-
}
|
|
922
901
|
if (ctrlOp === '??') return `if ((${targetCode} = ${exprCode}) == null) ${ctrlCode}`;
|
|
923
902
|
if (ctrlOp === '||') return `if (!(${targetCode} = ${exprCode})) ${ctrlCode}`;
|
|
924
903
|
return `if ((${targetCode} = ${exprCode})) ${ctrlCode}`;
|
|
@@ -962,8 +941,6 @@ export class CodeEmitter {
|
|
|
962
941
|
let unwrapped = Array.isArray(wrappedValue) && wrappedValue.length === 1 ? wrappedValue[0] : wrappedValue;
|
|
963
942
|
let fullValue = [binOp, left, unwrapped];
|
|
964
943
|
let t = this.emit(target, 'value'), c = this.emit(condition, 'value'), v = this.emit(fullValue, 'value');
|
|
965
|
-
let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
|
|
966
|
-
if (tgtName && this._inlineVarsPending?.delete(tgtName)) return `let ${t};\n${this.indent()}if (${c}) ${t} = ${v}`;
|
|
967
944
|
return `if (${c}) ${t} = ${v}`;
|
|
968
945
|
}
|
|
969
946
|
}
|
|
@@ -978,8 +955,6 @@ export class CodeEmitter {
|
|
|
978
955
|
let t = this.emit(target, 'value');
|
|
979
956
|
let condCode = this.unwrapLogical(this.emit(condition, 'value'));
|
|
980
957
|
let v = this.emit(unwrapped, 'value');
|
|
981
|
-
let tgtName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
|
|
982
|
-
if (tgtName && this._inlineVarsPending?.delete(tgtName)) return `let ${t};\n${this.indent()}if (${condCode}) ${t} = ${v}`;
|
|
983
958
|
return `if (${condCode}) ${t} = ${v}`;
|
|
984
959
|
}
|
|
985
960
|
}
|
|
@@ -1006,12 +981,6 @@ export class CodeEmitter {
|
|
|
1006
981
|
let isObjLit = this.is(value, 'object');
|
|
1007
982
|
if (!isObjLit) valueCode = this.unwrap(valueCode);
|
|
1008
983
|
|
|
1009
|
-
// Inline `let` at first assignment for variables classified as inlinable
|
|
1010
|
-
let targetName = (typeof target === 'string') ? target : (target instanceof String) ? str(target) : null;
|
|
1011
|
-
if (head === '=' && targetName && context === 'statement' && this._inlineVarsPending?.delete(targetName)) {
|
|
1012
|
-
return `let ${targetCode} = ${valueCode}`;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
984
|
let needsParensVal = context === 'value';
|
|
1016
985
|
let needsParensObj = context === 'statement' && this.is(target, 'object');
|
|
1017
986
|
if (needsParensVal || needsParensObj) return `(${targetCode} ${op} ${valueCode})`;
|
|
@@ -2442,19 +2411,9 @@ export class CodeEmitter {
|
|
|
2442
2411
|
this.restMiddleParam = null;
|
|
2443
2412
|
}
|
|
2444
2413
|
|
|
2445
|
-
// Classify variables: inline `let` at first assignment vs hoist to top
|
|
2446
|
-
let prevInlinePending = this._inlineVarsPending;
|
|
2447
|
-
let inlineVars = new Set();
|
|
2448
|
-
if (newVars.size > 0 && statements.length > 0) {
|
|
2449
|
-
let classified = this.classifyVarsForInlining(statements, newVars);
|
|
2450
|
-
inlineVars = classified.inlineVars;
|
|
2451
|
-
if (inlineVars.size > 0) this._inlineVarsPending = new Set(inlineVars);
|
|
2452
|
-
}
|
|
2453
|
-
|
|
2454
2414
|
this.indentLevel++;
|
|
2455
2415
|
let code = '{\n';
|
|
2456
|
-
|
|
2457
|
-
if (hoistVars.size > 0) code += this.indent() + `let ${Array.from(hoistVars).sort().join(', ')};\n`;
|
|
2416
|
+
if (newVars.size > 0) code += this.indent() + `let ${Array.from(newVars).sort().join(', ')};\n`;
|
|
2458
2417
|
|
|
2459
2418
|
let firstIsSuper = autoAssignments.length > 0 && statements.length > 0 &&
|
|
2460
2419
|
Array.isArray(statements[0]) && statements[0][0] === 'super';
|
|
@@ -2485,8 +2444,6 @@ export class CodeEmitter {
|
|
|
2485
2444
|
if (typeof target === 'string' && Array.isArray(value)) {
|
|
2486
2445
|
let vh = value[0];
|
|
2487
2446
|
if (vh === 'comprehension' || vh === 'for-in') {
|
|
2488
|
-
// Comprehension targets with inline vars: declare before the loop
|
|
2489
|
-
if (this._inlineVarsPending?.delete(target)) code += this.indent() + `let ${target};\n`;
|
|
2490
2447
|
this.comprehensionTarget = target;
|
|
2491
2448
|
code += this.emit(value, 'value');
|
|
2492
2449
|
this.comprehensionTarget = null;
|
|
@@ -2494,14 +2451,6 @@ export class CodeEmitter {
|
|
|
2494
2451
|
return;
|
|
2495
2452
|
}
|
|
2496
2453
|
}
|
|
2497
|
-
// Handle inline vars at last statement (auto-return): split into let + return
|
|
2498
|
-
if ((typeof target === 'string' || target instanceof String) && this._inlineVarsPending?.has(str(target))) {
|
|
2499
|
-
this._inlineVarsPending.delete(str(target));
|
|
2500
|
-
let assignCode = this.emit(stmt, 'statement');
|
|
2501
|
-
code += this.indent() + 'let ' + this.addSemicolon(stmt, assignCode) + '\n';
|
|
2502
|
-
code += this.indent() + `return ${str(target)};\n`;
|
|
2503
|
-
return;
|
|
2504
|
-
}
|
|
2505
2454
|
}
|
|
2506
2455
|
|
|
2507
2456
|
let needsReturn = !isConstructor && !sideEffectOnly && isLast &&
|
|
@@ -2530,7 +2479,6 @@ export class CodeEmitter {
|
|
|
2530
2479
|
if (!noRetStmts.includes(lastH)) code += this.indent() + 'return;\n';
|
|
2531
2480
|
}
|
|
2532
2481
|
|
|
2533
|
-
this._inlineVarsPending = prevInlinePending;
|
|
2534
2482
|
this.indentLevel--;
|
|
2535
2483
|
code += this.indent() + '}';
|
|
2536
2484
|
this.scopeStack.pop();
|
|
@@ -3163,197 +3111,6 @@ export class CodeEmitter {
|
|
|
3163
3111
|
return false;
|
|
3164
3112
|
}
|
|
3165
3113
|
|
|
3166
|
-
// ---------------------------------------------------------------------------
|
|
3167
|
-
// Variable inlining — emit `let` at first assignment instead of hoisting
|
|
3168
|
-
// ---------------------------------------------------------------------------
|
|
3169
|
-
|
|
3170
|
-
// Check if an s-expression references a variable name (stopping at function boundaries)
|
|
3171
|
-
referencesVar(sexpr, varName) {
|
|
3172
|
-
if (!sexpr) return false;
|
|
3173
|
-
if (sexpr instanceof String) return str(sexpr) === varName;
|
|
3174
|
-
if (typeof sexpr === 'string') return sexpr === varName;
|
|
3175
|
-
if (!Array.isArray(sexpr)) return false;
|
|
3176
|
-
let h = sexpr[0];
|
|
3177
|
-
let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
|
|
3178
|
-
if (hs === 'def' || hs === '->' || hs === '=>' || hs === 'effect') return false;
|
|
3179
|
-
// Property access: only check the object, not the property name
|
|
3180
|
-
if (hs === '.' || hs === '?.') return this.referencesVar(sexpr[1], varName);
|
|
3181
|
-
// Object literal: check values but not simple string keys
|
|
3182
|
-
if (hs === 'object') {
|
|
3183
|
-
for (let i = 1; i < sexpr.length; i++) {
|
|
3184
|
-
let pair = sexpr[i];
|
|
3185
|
-
if (Array.isArray(pair)) {
|
|
3186
|
-
if (this.is(pair, '...')) { if (this.referencesVar(pair[1], varName)) return true; }
|
|
3187
|
-
else if (pair.length >= 2) { if (this.referencesVar(pair[pair.length - 1], varName)) return true; }
|
|
3188
|
-
} else { if (this.referencesVar(pair, varName)) return true; }
|
|
3189
|
-
}
|
|
3190
|
-
return false;
|
|
3191
|
-
}
|
|
3192
|
-
return sexpr.some(item => this.referencesVar(item, varName));
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
// Check if the first reference to a variable in DFS order is a `=` assignment
|
|
3196
|
-
// at statement level (not inside a value expression like another assignment's RHS).
|
|
3197
|
-
// Returns true only when it's safe to emit `let` at the first assignment site.
|
|
3198
|
-
firstRefIsAssignment(sexpr, varName) {
|
|
3199
|
-
let result = null; // null = not found, 'write' = statement-level assignment, 'read' = read/value
|
|
3200
|
-
let isVar = (n) => (n instanceof String ? str(n) : n) === varName;
|
|
3201
|
-
let walk = (node, inValue) => {
|
|
3202
|
-
if (result !== null) return;
|
|
3203
|
-
if (!node) return;
|
|
3204
|
-
if (!Array.isArray(node)) { if (isVar(node)) result = 'read'; return; }
|
|
3205
|
-
let h = node[0];
|
|
3206
|
-
let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
|
|
3207
|
-
if (hs === 'def' || hs === '->' || hs === '=>' || hs === 'effect') return;
|
|
3208
|
-
// Assignment to our variable
|
|
3209
|
-
if (hs === '=' && (typeof node[1] === 'string' || node[1] instanceof String) && str(node[1]) === varName) {
|
|
3210
|
-
if (inValue) { result = 'read'; return; } // In value context — can't emit `let` here
|
|
3211
|
-
result = this.referencesVar(node[2], varName) ? 'read' : 'write';
|
|
3212
|
-
return;
|
|
3213
|
-
}
|
|
3214
|
-
// Compound assignment reads the variable
|
|
3215
|
-
if (CodeEmitter.ASSIGNMENT_OPS.has(hs) && hs !== '=' &&
|
|
3216
|
-
(typeof node[1] === 'string' || node[1] instanceof String) && str(node[1]) === varName) {
|
|
3217
|
-
result = 'read'; return;
|
|
3218
|
-
}
|
|
3219
|
-
// Property access: only check object
|
|
3220
|
-
if (hs === '.' || hs === '?.') { walk(node[1], inValue); return; }
|
|
3221
|
-
// Object literal: skip simple string keys
|
|
3222
|
-
if (hs === 'object') {
|
|
3223
|
-
for (let i = 1; i < node.length; i++) {
|
|
3224
|
-
let pair = node[i];
|
|
3225
|
-
if (Array.isArray(pair)) {
|
|
3226
|
-
if (this.is(pair, '...')) walk(pair[1], true);
|
|
3227
|
-
else if (pair.length >= 2) walk(pair[pair.length - 1], true);
|
|
3228
|
-
} else walk(pair, true);
|
|
3229
|
-
}
|
|
3230
|
-
return;
|
|
3231
|
-
}
|
|
3232
|
-
// Block: children are at statement level
|
|
3233
|
-
if (hs === 'block') { for (let i = 1; i < node.length; i++) walk(node[i], false); return; }
|
|
3234
|
-
// If: condition is value, branches maintain parent context
|
|
3235
|
-
if (hs === 'if') { walk(node[1], true); for (let i = 2; i < node.length; i++) walk(node[i], inValue); return; }
|
|
3236
|
-
// Loops: iterable/condition is value, body is statement
|
|
3237
|
-
if (hs === 'for-in' || hs === 'for-of' || hs === 'for-as') { walk(node[2], true); if (node.length > 3) walk(node[node.length - 1], false); return; }
|
|
3238
|
-
if (hs === 'while') { walk(node[1], true); walk(node[2], false); return; }
|
|
3239
|
-
// Try: blocks are statement level
|
|
3240
|
-
if (hs === 'try') { for (let i = 1; i < node.length; i++) walk(node[i], false); return; }
|
|
3241
|
-
// Other assignments: RHS is value context
|
|
3242
|
-
if (CodeEmitter.ASSIGNMENT_OPS.has(hs)) { walk(node[2], true); return; }
|
|
3243
|
-
// Default: everything else is value context
|
|
3244
|
-
for (let i = 0; i < node.length; i++) walk(node[i], true);
|
|
3245
|
-
};
|
|
3246
|
-
walk(sexpr, false); // top of a statement is statement context
|
|
3247
|
-
return result === 'write';
|
|
3248
|
-
}
|
|
3249
|
-
|
|
3250
|
-
// Check if all references to a variable within a statement are contained in a
|
|
3251
|
-
// single block child. Returns false if the variable appears at multiple nesting
|
|
3252
|
-
// levels (e.g., inside an if-branch AND at the same level as the if).
|
|
3253
|
-
allRefsInSingleBlock(sexpr, varName) {
|
|
3254
|
-
if (!Array.isArray(sexpr)) return false;
|
|
3255
|
-
let h = sexpr[0];
|
|
3256
|
-
let hs = (typeof h === 'string') ? h : (h instanceof String) ? str(h) : null;
|
|
3257
|
-
// For 'if': check condition + each branch. Variable must appear in exactly one branch,
|
|
3258
|
-
// and NOT in the condition. Then recurse into that branch.
|
|
3259
|
-
if (hs === 'if') {
|
|
3260
|
-
if (this.referencesVar(sexpr[1], varName)) return false; // in condition
|
|
3261
|
-
let branchCount = 0;
|
|
3262
|
-
let refBranch = null;
|
|
3263
|
-
for (let i = 2; i < sexpr.length; i++) {
|
|
3264
|
-
if (this.referencesVar(sexpr[i], varName)) { branchCount++; refBranch = sexpr[i]; }
|
|
3265
|
-
}
|
|
3266
|
-
if (branchCount !== 1) return false;
|
|
3267
|
-
return this.allRefsInSingleBlock(refBranch, varName);
|
|
3268
|
-
}
|
|
3269
|
-
// For 'block': variable must appear in only one child statement.
|
|
3270
|
-
// Then recurse into that child.
|
|
3271
|
-
if (hs === 'block') {
|
|
3272
|
-
let childCount = 0;
|
|
3273
|
-
let refChild = null;
|
|
3274
|
-
for (let i = 1; i < sexpr.length; i++) {
|
|
3275
|
-
if (this.referencesVar(sexpr[i], varName)) { childCount++; refChild = sexpr[i]; }
|
|
3276
|
-
}
|
|
3277
|
-
if (childCount !== 1) return false;
|
|
3278
|
-
// If the single child IS a direct assignment to this variable, we've bottomed out — safe.
|
|
3279
|
-
if (Array.isArray(refChild) && refChild[0] === '=' &&
|
|
3280
|
-
(typeof refChild[1] === 'string' || refChild[1] instanceof String) &&
|
|
3281
|
-
str(refChild[1]) === varName) return true;
|
|
3282
|
-
return this.allRefsInSingleBlock(refChild, varName);
|
|
3283
|
-
}
|
|
3284
|
-
// For loops: iterable/condition must not reference var, body is a block — recurse.
|
|
3285
|
-
if (hs === 'for-in' || hs === 'for-of' || hs === 'for-as') {
|
|
3286
|
-
if (this.referencesVar(sexpr[2], varName)) return false;
|
|
3287
|
-
return true;
|
|
3288
|
-
}
|
|
3289
|
-
if (hs === 'while') {
|
|
3290
|
-
if (this.referencesVar(sexpr[1], varName)) return false;
|
|
3291
|
-
return true;
|
|
3292
|
-
}
|
|
3293
|
-
// For try: recurse
|
|
3294
|
-
if (hs === 'try') return true;
|
|
3295
|
-
return false;
|
|
3296
|
-
}
|
|
3297
|
-
|
|
3298
|
-
// Classify variables into those that can have `let` inlined at their first assignment
|
|
3299
|
-
// vs those that must be hoisted to the function/program top.
|
|
3300
|
-
classifyVarsForInlining(statements, vars) {
|
|
3301
|
-
if (vars.size === 0) return { inlineVars: new Set(), hoistVars: new Set(vars) };
|
|
3302
|
-
|
|
3303
|
-
// Pre-scan: variables with type annotations (::) must be hoisted. The DTS header
|
|
3304
|
-
// declares them as `let x: Type;` — inlining `let x = value;` in the body would
|
|
3305
|
-
// create a duplicate declaration, causing TS2454 ("used before being assigned").
|
|
3306
|
-
let typedVars = new Set();
|
|
3307
|
-
let findTypedAssigns = (node) => {
|
|
3308
|
-
if (!Array.isArray(node)) return;
|
|
3309
|
-
if (node[0] === '=' && node[1] instanceof String && node[1].type && vars.has(str(node[1]))) {
|
|
3310
|
-
typedVars.add(str(node[1]));
|
|
3311
|
-
}
|
|
3312
|
-
for (let i = 1; i < node.length; i++) findTypedAssigns(node[i]);
|
|
3313
|
-
};
|
|
3314
|
-
for (let stmt of statements) findTypedAssigns(stmt);
|
|
3315
|
-
|
|
3316
|
-
// For each variable, find which statement indices reference it
|
|
3317
|
-
let varStmts = new Map();
|
|
3318
|
-
for (let v of vars) varStmts.set(v, []);
|
|
3319
|
-
for (let i = 0; i < statements.length; i++) {
|
|
3320
|
-
for (let v of vars) {
|
|
3321
|
-
if (this.referencesVar(statements[i], v)) varStmts.get(v).push(i);
|
|
3322
|
-
}
|
|
3323
|
-
}
|
|
3324
|
-
let inlineVars = new Set(), hoistVars = new Set();
|
|
3325
|
-
for (let [v, indices] of varStmts) {
|
|
3326
|
-
if (indices.length === 0) { hoistVars.add(v); continue; }
|
|
3327
|
-
// Typed variables must be hoisted — DTS header already declares them
|
|
3328
|
-
if (typedVars.has(v)) { hoistVars.add(v); continue; }
|
|
3329
|
-
let firstIdx = indices[0];
|
|
3330
|
-
let firstStmt = statements[firstIdx];
|
|
3331
|
-
// Check if the first statement containing this variable is a direct `=` assignment to it
|
|
3332
|
-
let isDirectAssign = Array.isArray(firstStmt) && firstStmt[0] === '=' &&
|
|
3333
|
-
(typeof firstStmt[1] === 'string' || firstStmt[1] instanceof String) &&
|
|
3334
|
-
str(firstStmt[1]) === v &&
|
|
3335
|
-
!this.referencesVar(firstStmt[2], v);
|
|
3336
|
-
if (isDirectAssign) {
|
|
3337
|
-
// First reference is a direct body-level assignment — safe to inline `let` here
|
|
3338
|
-
// (same scope as hoisted `let`, but TS can infer the type from the initializer)
|
|
3339
|
-
inlineVars.add(v);
|
|
3340
|
-
} else if (indices.length === 1 && !this.is(firstStmt, 'switch') &&
|
|
3341
|
-
this.allRefsInSingleBlock(firstStmt, v) &&
|
|
3342
|
-
this.firstRefIsAssignment(firstStmt, v)) {
|
|
3343
|
-
// All references are within a single block branch of a non-switch statement
|
|
3344
|
-
// (e.g., all inside one if-branch, one for body, etc.) and the first DFS
|
|
3345
|
-
// reference is a direct assignment. JS 'let' block scoping is safe here
|
|
3346
|
-
// because the variable never escapes that single block.
|
|
3347
|
-
inlineVars.add(v);
|
|
3348
|
-
} else {
|
|
3349
|
-
// Variable spans multiple statements, is in a switch, or first ref is a read.
|
|
3350
|
-
// Must hoist to ensure correct scoping.
|
|
3351
|
-
hoistVars.add(v);
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
|
-
return { inlineVars, hoistVars };
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
3114
|
// Class helpers
|
|
3358
3115
|
extractMemberName(mk) {
|
|
3359
3116
|
if (this.is(mk, '.') && mk[1] === 'this') return mk[2];
|
package/src/typecheck.js
CHANGED
|
@@ -916,6 +916,59 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
|
|
|
916
916
|
headerDts = ARIA_TYPE_DECLS.join('\n') + '\n' + headerDts;
|
|
917
917
|
}
|
|
918
918
|
|
|
919
|
+
// Inline hoisted `let` declarations at their first assignment in the shadow
|
|
920
|
+
// TS file. Rip always hoists `let` to the top of scope for correct JS
|
|
921
|
+
// scoping. For TypeScript inference, we move the declaration inline so TS
|
|
922
|
+
// can infer types from initializers (e.g., `let x; x = 1` → `let x = 1`).
|
|
923
|
+
// Only rewrites same-scope, straight-line assignments — stops at control
|
|
924
|
+
// flow to avoid changing binding visibility in the shadow file.
|
|
925
|
+
if (hasTypes && code) {
|
|
926
|
+
const cl = code.split('\n');
|
|
927
|
+
for (let i = 0; i < cl.length; i++) {
|
|
928
|
+
const m = cl[i].match(/^(\s*)let\s+([A-Za-z_$][\w$]*(?:\s*,\s*[A-Za-z_$][\w$]*)*)\s*;\s*$/);
|
|
929
|
+
if (!m) continue;
|
|
930
|
+
// Only process hoist-position lets (first non-blank line after `{` or start of file)
|
|
931
|
+
let prev = null;
|
|
932
|
+
for (let k = i - 1; k >= 0; k--) { if (cl[k].trim() !== '') { prev = cl[k]; break; } }
|
|
933
|
+
if (prev !== null && !/\{\s*$/.test(prev)) continue;
|
|
934
|
+
|
|
935
|
+
const indent = m[1];
|
|
936
|
+
const vars = m[2].split(/\s*,\s*/);
|
|
937
|
+
const inlined = new Set();
|
|
938
|
+
const bailed = new Set();
|
|
939
|
+
const reEsc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
940
|
+
|
|
941
|
+
for (let j = i + 1; j < cl.length; j++) {
|
|
942
|
+
const line = cl[j];
|
|
943
|
+
if (line.trim() === '') continue;
|
|
944
|
+
// End of scope
|
|
945
|
+
if (new RegExp('^' + reEsc(indent) + '}').test(line)) break;
|
|
946
|
+
// Skip deeper-indented lines
|
|
947
|
+
if (line.startsWith(indent + ' ')) continue;
|
|
948
|
+
// Stop at structural statements
|
|
949
|
+
if (new RegExp('^' + reEsc(indent) + '(?:if\\b|for\\b|while\\b|switch\\b|try\\b|catch\\b|finally\\b|function\\b|class\\b|do\\b|\\{|\\})').test(line)) break;
|
|
950
|
+
|
|
951
|
+
for (const v of vars) {
|
|
952
|
+
if (inlined.has(v) || bailed.has(v)) continue;
|
|
953
|
+
const ve = reEsc(v);
|
|
954
|
+
const assignRe = new RegExp('^' + reEsc(indent) + ve + '\\s*=\\s*(.*);\\s*$');
|
|
955
|
+
const assign = line.match(assignRe);
|
|
956
|
+
if (assign) {
|
|
957
|
+
cl[j] = `${indent}let ${v} = ${assign[1]};`;
|
|
958
|
+
inlined.add(v);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
if (new RegExp('\\b' + ve + '\\b').test(line)) bailed.add(v);
|
|
962
|
+
}
|
|
963
|
+
if (vars.every(v => inlined.has(v) || bailed.has(v))) break;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const remaining = vars.filter(v => !inlined.has(v));
|
|
967
|
+
cl[i] = remaining.length ? `${indent}let ${remaining.join(', ')};` : '';
|
|
968
|
+
}
|
|
969
|
+
code = cl.filter(l => l !== '').join('\n');
|
|
970
|
+
}
|
|
971
|
+
|
|
919
972
|
let tsContent = (hasTypes ? headerDts + '\n' : '') + code;
|
|
920
973
|
const headerLines = hasTypes ? countLines(headerDts + '\n') : 1;
|
|
921
974
|
|