skittles 1.2.7 → 1.3.1

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.
@@ -11,10 +11,17 @@ export function generateSolidityFile(contracts, imports) {
11
11
  parts.push("// SPDX-License-Identifier: MIT");
12
12
  parts.push("pragma solidity ^0.8.20;");
13
13
  parts.push("");
14
+ // Add Hardhat console import if any contract uses console.log
15
+ const needsConsoleImport = contracts.some(contractUsesConsoleLog);
16
+ if (needsConsoleImport) {
17
+ parts.push('import "hardhat/console.sol";');
18
+ }
14
19
  if (imports && imports.length > 0) {
15
20
  for (const imp of imports) {
16
21
  parts.push(`import "${imp}";`);
17
22
  }
23
+ }
24
+ if (needsConsoleImport || (imports && imports.length > 0)) {
18
25
  parts.push("");
19
26
  }
20
27
  // Collect and deduplicate contract interfaces across all contracts
@@ -63,8 +70,36 @@ export function generateSolidityFile(contracts, imports) {
63
70
  parts.push(generateInterfaceDecl(iface));
64
71
  parts.push("");
65
72
  }
73
+ // Build per-contract ancestor sets so deduplication only applies when the
74
+ // current contract inherits from the contract that first emitted a definition.
75
+ const contractByName = new Map(contracts.map((c) => [c.name, c]));
76
+ const ancestorsMap = new Map();
77
+ for (const contract of contracts) {
78
+ const ancestors = new Set();
79
+ const queue = [...contract.inherits.filter((n) => contractByName.has(n))];
80
+ let queueIndex = 0;
81
+ while (queueIndex < queue.length) {
82
+ const name = queue[queueIndex++];
83
+ if (ancestors.has(name))
84
+ continue;
85
+ ancestors.add(name);
86
+ const parent = contractByName.get(name);
87
+ if (parent) {
88
+ for (const gp of parent.inherits) {
89
+ if (contractByName.has(gp))
90
+ queue.push(gp);
91
+ }
92
+ }
93
+ }
94
+ ancestorsMap.set(contract.name, ancestors);
95
+ }
96
+ // Track all contracts that emitted each definition / function so child
97
+ // contracts that inherit from any of those origins can skip re-emission.
98
+ const definitionOrigins = new Map();
99
+ const functionOrigins = new Map();
66
100
  for (let i = 0; i < contracts.length; i++) {
67
- parts.push(generateContractBody(contracts[i], emittedFileScopeTypes));
101
+ const ancestors = ancestorsMap.get(contracts[i].name);
102
+ parts.push(generateContractBody(contracts[i], emittedFileScopeTypes, definitionOrigins, functionOrigins, ancestors));
68
103
  if (i < contracts.length - 1) {
69
104
  parts.push("");
70
105
  }
@@ -75,28 +110,49 @@ export function generateSolidityFile(contracts, imports) {
75
110
  export function generateSolidity(contract, imports) {
76
111
  return generateSolidityFile([contract], imports);
77
112
  }
78
- function generateContractBody(contract, fileScopeTypes = new Set()) {
113
+ function generateContractBody(contract, fileScopeTypes = new Set(), definitionOrigins = new Map(), functionOrigins = new Map(), ancestors = new Set()) {
79
114
  const parts = [];
80
115
  const inheritance = contract.inherits.length > 0
81
116
  ? ` is ${contract.inherits.join(", ")}`
82
117
  : "";
83
- parts.push(`contract ${contract.name}${inheritance} {`);
118
+ const abstractPrefix = contract.isAbstract ? "abstract " : "";
119
+ parts.push(`${abstractPrefix}contract ${contract.name}${inheritance} {`);
120
+ const hasAncestorOrigin = (origins) => origins !== undefined && Array.from(origins).some((o) => ancestors.has(o));
121
+ const addOrigin = (map, key) => {
122
+ let origins = map.get(key);
123
+ if (!origins) {
124
+ origins = new Set();
125
+ map.set(key, origins);
126
+ }
127
+ origins.add(contract.name);
128
+ };
84
129
  for (const en of contract.enums ?? []) {
85
130
  if (fileScopeTypes.has(en.name))
86
131
  continue;
132
+ if (hasAncestorOrigin(definitionOrigins.get(en.name)))
133
+ continue;
134
+ addOrigin(definitionOrigins, en.name);
87
135
  parts.push(` enum ${en.name} { ${en.members.join(", ")} }`);
88
136
  parts.push("");
89
137
  }
138
+ let emittedCustomErrorCount = 0;
90
139
  for (const ce of contract.customErrors ?? []) {
140
+ if (hasAncestorOrigin(definitionOrigins.get(ce.name)))
141
+ continue;
142
+ addOrigin(definitionOrigins, ce.name);
91
143
  const params = ce.parameters.map((p) => `${generateType(p.type)} ${p.name}`).join(", ");
92
144
  parts.push(` error ${ce.name}(${params});`);
145
+ emittedCustomErrorCount++;
93
146
  }
94
- if ((contract.customErrors ?? []).length > 0) {
147
+ if (emittedCustomErrorCount > 0) {
95
148
  parts.push("");
96
149
  }
97
150
  for (const s of contract.structs ?? []) {
98
151
  if (fileScopeTypes.has(s.name))
99
152
  continue;
153
+ if (hasAncestorOrigin(definitionOrigins.get(s.name)))
154
+ continue;
155
+ addOrigin(definitionOrigins, s.name);
100
156
  parts.push(generateStructDecl(s));
101
157
  parts.push("");
102
158
  }
@@ -109,19 +165,43 @@ function generateContractBody(contract, fileScopeTypes = new Set()) {
109
165
  for (const v of contract.variables) {
110
166
  parts.push(` ${generateVariable(v)}`);
111
167
  }
168
+ const readonlyArrayVars = contract.variables.filter((v) => v.immutable && v.type.kind === SkittlesTypeKind.Array);
169
+ // Skip functions already emitted by an ancestor contract in the same file
170
+ // (shared file-level functions injected into both parent and child), unless
171
+ // the child explicitly overrides them. Use a full signature key (name +
172
+ // full parameter types) so overloads are not incorrectly suppressed.
173
+ const getFunctionKey = (f) => {
174
+ const paramTypes = f.parameters
175
+ .map((p) => (p.type ? generateType(p.type) : "unknown"))
176
+ .join(",");
177
+ return `${f.name}(${paramTypes})`;
178
+ };
179
+ const functionsToEmit = contract.functions.filter((f) => {
180
+ const key = getFunctionKey(f);
181
+ return !hasAncestorOrigin(functionOrigins.get(key)) || f.isOverride;
182
+ });
183
+ for (const f of functionsToEmit) {
184
+ addOrigin(functionOrigins, getFunctionKey(f));
185
+ }
112
186
  if (contract.variables.length > 0 &&
113
- (contract.ctor || contract.functions.length > 0)) {
187
+ (contract.ctor || functionsToEmit.length > 0 || readonlyArrayVars.length > 0)) {
114
188
  parts.push("");
115
189
  }
116
190
  if (contract.ctor) {
117
- parts.push(generateConstructor(contract.ctor));
118
- if (contract.functions.length > 0) {
191
+ parts.push(generateConstructor(contract.ctor, contract.inherits));
192
+ if (functionsToEmit.length > 0 || readonlyArrayVars.length > 0) {
119
193
  parts.push("");
120
194
  }
121
195
  }
122
- for (let i = 0; i < contract.functions.length; i++) {
123
- parts.push(generateFunction(contract.functions[i]));
124
- if (i < contract.functions.length - 1) {
196
+ for (let i = 0; i < functionsToEmit.length; i++) {
197
+ parts.push(generateFunction(functionsToEmit[i]));
198
+ if (i < functionsToEmit.length - 1 || readonlyArrayVars.length > 0) {
199
+ parts.push("");
200
+ }
201
+ }
202
+ for (let i = 0; i < readonlyArrayVars.length; i++) {
203
+ parts.push(generateReadonlyArrayGetter(readonlyArrayVars[i]));
204
+ if (i < readonlyArrayVars.length - 1) {
125
205
  parts.push("");
126
206
  }
127
207
  }
@@ -165,6 +245,11 @@ function collectReferencedTypeNames(type, structs, enums) {
165
245
  collectReferencedTypeNames(type.keyType, structs, enums);
166
246
  collectReferencedTypeNames(type.valueType, structs, enums);
167
247
  }
248
+ else if (type.kind === SkittlesTypeKind.Tuple && type.tupleTypes) {
249
+ for (const t of type.tupleTypes) {
250
+ collectReferencedTypeNames(t, structs, enums);
251
+ }
252
+ }
168
253
  }
169
254
  function generateInterfaceDecl(iface) {
170
255
  const lines = [];
@@ -178,7 +263,13 @@ function generateInterfaceDecl(iface) {
178
263
  : "";
179
264
  let returns = "";
180
265
  if (f.returnType && f.returnType.kind !== SkittlesTypeKind.Void) {
181
- returns = ` returns (${generateParamType(f.returnType)})`;
266
+ if (f.returnType.kind === SkittlesTypeKind.Tuple) {
267
+ const tupleParams = (f.returnType.tupleTypes ?? []).map(generateParamType).join(", ");
268
+ returns = ` returns (${tupleParams})`;
269
+ }
270
+ else {
271
+ returns = ` returns (${generateParamType(f.returnType)})`;
272
+ }
182
273
  }
183
274
  lines.push(` function ${f.name}(${params}) external${mut}${returns};`);
184
275
  }
@@ -205,7 +296,8 @@ function isValueType(type) {
205
296
  }
206
297
  function generateVariable(v) {
207
298
  const type = generateType(v.type);
208
- const vis = mapVisibility(v.visibility);
299
+ const isReadonlyArray = v.immutable && v.type.kind === SkittlesTypeKind.Array;
300
+ const vis = isReadonlyArray ? "internal" : mapVisibility(v.visibility);
209
301
  let modifier = "";
210
302
  if (v.constant) {
211
303
  modifier = " constant";
@@ -223,6 +315,16 @@ function generateVariable(v) {
223
315
  }
224
316
  return `${type} ${vis}${modifier}${overrideStr} ${v.name};`;
225
317
  }
318
+ function generateReadonlyArrayGetter(v) {
319
+ const type = generateType(v.type);
320
+ const name = v.name;
321
+ const getterName = `get${name.charAt(0).toUpperCase()}${name.slice(1)}`;
322
+ const lines = [];
323
+ lines.push(` function ${getterName}() public view returns (${type} memory) {`);
324
+ lines.push(` return ${name};`);
325
+ lines.push(" }");
326
+ return lines.join("\n");
327
+ }
226
328
  function generateFunction(f) {
227
329
  if (f.name === "receive") {
228
330
  const lines = [];
@@ -250,23 +352,73 @@ function generateFunction(f) {
250
352
  const virtOverride = f.isOverride ? " override" : f.isVirtual ? " virtual" : "";
251
353
  let returns = "";
252
354
  if (f.returnType && f.returnType.kind !== SkittlesTypeKind.Void) {
253
- returns = ` returns (${generateParamType(f.returnType)})`;
355
+ if (f.returnType.kind === SkittlesTypeKind.Tuple) {
356
+ const tupleParams = (f.returnType.tupleTypes ?? []).map(generateParamType).join(", ");
357
+ returns = ` returns (${tupleParams})`;
358
+ }
359
+ else {
360
+ returns = ` returns (${generateParamType(f.returnType)})`;
361
+ }
254
362
  }
255
363
  const lines = [];
256
- lines.push(` function ${f.name}(${params}) ${vis}${mut}${virtOverride}${returns} {`);
257
- for (const s of f.body) {
258
- lines.push(generateStatement(s, " "));
364
+ if (f.isAbstract) {
365
+ lines.push(` function ${f.name}(${params}) ${vis}${mut}${virtOverride}${returns};`);
366
+ }
367
+ else {
368
+ lines.push(` function ${f.name}(${params}) ${vis}${mut}${virtOverride}${returns} {`);
369
+ for (const s of f.body) {
370
+ lines.push(generateStatement(s, " "));
371
+ }
372
+ lines.push(" }");
259
373
  }
260
- lines.push(" }");
261
374
  return lines.join("\n");
262
375
  }
263
- function generateConstructor(c) {
264
- const params = c.parameters
376
+ function isSuperCall(stmt) {
377
+ return (stmt.kind === "expression" &&
378
+ stmt.expression.kind === "call" &&
379
+ stmt.expression.callee.kind === "identifier" &&
380
+ stmt.expression.callee.name === "super");
381
+ }
382
+ function getSuperCallArgs(stmt) {
383
+ if (stmt.kind === "expression" &&
384
+ stmt.expression.kind === "call" &&
385
+ stmt.expression.callee.kind === "identifier" &&
386
+ stmt.expression.callee.name === "super") {
387
+ return stmt.expression.args;
388
+ }
389
+ return null;
390
+ }
391
+ function generateConstructor(c, inherits = []) {
392
+ const regularParams = c.parameters.filter((p) => !p.defaultValue);
393
+ const defaultParams = c.parameters.filter((p) => p.defaultValue);
394
+ const params = regularParams
265
395
  .map((p) => `${generateParamType(p.type)} ${p.name}`)
266
396
  .join(", ");
397
+ // Extract super() call(s) from the body and validate.
398
+ const superCalls = c.body.filter(isSuperCall);
399
+ if (superCalls.length > 1) {
400
+ throw new Error("Constructor contains multiple super() calls, but only one is allowed");
401
+ }
402
+ const bodyWithoutSuper = c.body.filter((s) => !isSuperCall(s));
403
+ let parentModifier = "";
404
+ if (superCalls.length === 1) {
405
+ const args = getSuperCallArgs(superCalls[0]);
406
+ if (args.length > 0) {
407
+ if (inherits.length === 0) {
408
+ throw new Error("Constructor contains a super(...) call, but no parent contract is specified in 'inherits'");
409
+ }
410
+ if (defaultParams.length > 0) {
411
+ throw new Error("super(...) with constructor parameters that have default values is not supported");
412
+ }
413
+ parentModifier = ` ${inherits[0]}(${args.map(generateExpression).join(", ")})`;
414
+ }
415
+ }
267
416
  const lines = [];
268
- lines.push(` constructor(${params}) {`);
269
- for (const s of c.body) {
417
+ lines.push(` constructor(${params})${parentModifier} {`);
418
+ for (const p of defaultParams) {
419
+ lines.push(` ${generateParamType(p.type)} ${p.name} = ${generateExpression(p.defaultValue)};`);
420
+ }
421
+ for (const s of bodyWithoutSuper) {
270
422
  lines.push(generateStatement(s, " "));
271
423
  }
272
424
  lines.push(" }");
@@ -301,6 +453,8 @@ export function generateType(type) {
301
453
  return type.structName ?? "UnknownInterface";
302
454
  case SkittlesTypeKind.Enum:
303
455
  return type.structName ?? "UnknownEnum";
456
+ case SkittlesTypeKind.Tuple:
457
+ return `(${(type.tupleTypes ?? []).map(generateType).join(", ")})`;
304
458
  case SkittlesTypeKind.Void:
305
459
  return "";
306
460
  default:
@@ -382,6 +536,16 @@ export function generateExpression(expr) {
382
536
  const callResult = tryGenerateBuiltinCall(expr);
383
537
  if (callResult)
384
538
  return callResult;
539
+ // addr.transfer(amount) → payable(addr).transfer(amount)
540
+ // Exclude this.transfer(...) (internal call) and this.stateVar.transfer(...)
541
+ // (external contract interface call) to avoid misclassifying non-ETH transfers.
542
+ if (expr.callee.kind === "property-access" &&
543
+ expr.callee.property === "transfer" &&
544
+ expr.args.length === 1 &&
545
+ !isThisOrContractCall(expr.callee.object)) {
546
+ const addr = generateExpression(expr.callee.object);
547
+ return `payable(${addr}).transfer(${generateExpression(expr.args[0])})`;
548
+ }
385
549
  return `${generateExpression(expr.callee)}(${expr.args.map(generateExpression).join(", ")})`;
386
550
  }
387
551
  case "conditional":
@@ -392,6 +556,8 @@ export function generateExpression(expr) {
392
556
  const values = expr.properties.map((p) => generateExpression(p.value)).join(", ");
393
557
  return values;
394
558
  }
559
+ case "tuple-literal":
560
+ return `(${expr.elements.map(generateExpression).join(", ")})`;
395
561
  default:
396
562
  return "/* unsupported */";
397
563
  }
@@ -419,8 +585,19 @@ export function generateStatement(stmt, indent) {
419
585
  }
420
586
  return `${indent}${type} ${stmt.name};`;
421
587
  }
422
- case "expression":
588
+ case "expression": {
589
+ if (stmt.expression.kind === "conditional" && indent) {
590
+ const conditionalExpr = stmt.expression;
591
+ const lines = [];
592
+ lines.push(`${indent}if (${generateExpression(conditionalExpr.condition)}) {`);
593
+ lines.push(generateStatement({ kind: "expression", expression: conditionalExpr.whenTrue }, inner));
594
+ lines.push(`${indent}} else {`);
595
+ lines.push(generateStatement({ kind: "expression", expression: conditionalExpr.whenFalse }, inner));
596
+ lines.push(`${indent}}`);
597
+ return lines.join("\n");
598
+ }
423
599
  return `${indent}${generateExpression(stmt.expression)};`;
600
+ }
424
601
  case "if": {
425
602
  if (isRequirePattern(stmt)) {
426
603
  const negated = negateExpression(stmt.condition);
@@ -532,6 +709,26 @@ export function generateStatement(stmt, indent) {
532
709
  lines.push(`${indent}}`);
533
710
  return lines.join("\n");
534
711
  }
712
+ case "try-catch": {
713
+ const lines = [];
714
+ const callExpr = generateExpression(stmt.call);
715
+ let returns = "";
716
+ if (stmt.returnVarName && stmt.returnType && stmt.returnType.kind !== SkittlesTypeKind.Void) {
717
+ returns = ` returns (${generateType(stmt.returnType)} ${stmt.returnVarName})`;
718
+ }
719
+ lines.push(`${indent}try ${callExpr}${returns} {`);
720
+ for (const s of stmt.successBody) {
721
+ lines.push(generateStatement(s, inner));
722
+ }
723
+ lines.push(`${indent}} catch {`);
724
+ for (const s of stmt.catchBody) {
725
+ lines.push(generateStatement(s, inner));
726
+ }
727
+ lines.push(`${indent}}`);
728
+ return lines.join("\n");
729
+ }
730
+ case "console-log":
731
+ return `${indent}console.log(${stmt.args.map(generateExpression).join(", ")});`;
535
732
  default:
536
733
  return `${indent}// unsupported statement`;
537
734
  }
@@ -570,6 +767,16 @@ function negateOperator(op) {
570
767
  };
571
768
  return map[op] ?? null;
572
769
  }
770
+ /**
771
+ * Check if a receiver expression is `this` (an internal contract method call).
772
+ * Used to avoid wrapping `this.transfer(...)` calls in `payable(...)`.
773
+ * Note: `this.stateVar.transfer(amount)` is NOT excluded here because
774
+ * codegen strips `this.` and the arg count (1 for ETH transfer vs 2+ for
775
+ * contract interface calls) serves as the discriminator.
776
+ */
777
+ function isThisOrContractCall(receiver) {
778
+ return receiver.kind === "identifier" && receiver.name === "this";
779
+ }
573
780
  // ============================================================
574
781
  // Built-in function recognition
575
782
  // ============================================================
@@ -597,8 +804,13 @@ function tryGenerateBuiltinCall(expr) {
597
804
  return `abi.encode(${args})`;
598
805
  case "abi.encodePacked":
599
806
  return `abi.encodePacked(${args})`;
600
- case "abi.decode":
807
+ case "abi.decode": {
808
+ if (expr.typeArgs && expr.typeArgs.length > 0) {
809
+ const types = expr.typeArgs.map(generateType).join(", ");
810
+ return `abi.decode(${args}, (${types}))`;
811
+ }
601
812
  return `abi.decode(${args})`;
813
+ }
602
814
  case "ecrecover":
603
815
  return `ecrecover(${args})`;
604
816
  case "addmod":
@@ -609,6 +821,11 @@ function tryGenerateBuiltinCall(expr) {
609
821
  return `assert(${args})`;
610
822
  case "gasleft":
611
823
  return `gasleft()`;
824
+ case "Contract":
825
+ if (expr.typeArgs && expr.typeArgs.length > 0 && expr.typeArgs[0].kind === SkittlesTypeKind.ContractInterface && expr.typeArgs[0].structName) {
826
+ return `${expr.typeArgs[0].structName}(${args})`;
827
+ }
828
+ throw new Error("Contract<T>() requires a contract interface type argument, e.g. Contract<IToken>(address)");
612
829
  case "string.concat":
613
830
  return `string.concat(${args})`;
614
831
  case "bytes.concat":
@@ -618,6 +835,47 @@ function tryGenerateBuiltinCall(expr) {
618
835
  }
619
836
  }
620
837
  // ============================================================
838
+ // Console.log detection
839
+ // ============================================================
840
+ function statementsUseConsoleLog(stmts) {
841
+ for (const stmt of stmts) {
842
+ if (stmt.kind === "console-log")
843
+ return true;
844
+ if (stmt.kind === "if") {
845
+ if (statementsUseConsoleLog(stmt.thenBody))
846
+ return true;
847
+ if (stmt.elseBody && statementsUseConsoleLog(stmt.elseBody))
848
+ return true;
849
+ }
850
+ if (stmt.kind === "for" || stmt.kind === "while" || stmt.kind === "do-while") {
851
+ if (statementsUseConsoleLog(stmt.body))
852
+ return true;
853
+ }
854
+ if (stmt.kind === "switch") {
855
+ for (const c of stmt.cases) {
856
+ if (statementsUseConsoleLog(c.body))
857
+ return true;
858
+ }
859
+ }
860
+ if (stmt.kind === "try-catch") {
861
+ if (statementsUseConsoleLog(stmt.successBody))
862
+ return true;
863
+ if (statementsUseConsoleLog(stmt.catchBody))
864
+ return true;
865
+ }
866
+ }
867
+ return false;
868
+ }
869
+ function contractUsesConsoleLog(contract) {
870
+ for (const f of contract.functions) {
871
+ if (statementsUseConsoleLog(f.body))
872
+ return true;
873
+ }
874
+ if (contract.ctor && statementsUseConsoleLog(contract.ctor.body))
875
+ return true;
876
+ return false;
877
+ }
878
+ // ============================================================
621
879
  // Visibility mapping (Skittles simplification)
622
880
  // ============================================================
623
881
  function mapVisibility(vis) {
@@ -625,4 +883,130 @@ function mapVisibility(vis) {
625
883
  return "public";
626
884
  return "internal";
627
885
  }
886
+ // ============================================================
887
+ // Source map generation
888
+ // ============================================================
889
+ /**
890
+ * Build a source map that maps generated Solidity line numbers
891
+ * back to TypeScript source line numbers.
892
+ *
893
+ * The mapping is built by walking the IR (which has source line info
894
+ * from the parser) and counting lines in the generated Solidity output
895
+ * to correlate each Solidity line with its TypeScript origin.
896
+ */
897
+ export function buildSourceMap(solidity, contracts, sourceFile) {
898
+ const solLines = solidity.split("\n");
899
+ const mappings = {};
900
+ let lineIdx = 0; // 0-based index into solLines
901
+ // Helper: find the next line matching a test, starting from lineIdx
902
+ function findLine(test) {
903
+ for (let i = lineIdx; i < solLines.length; i++) {
904
+ if (test(solLines[i])) {
905
+ lineIdx = i;
906
+ return i;
907
+ }
908
+ }
909
+ return -1;
910
+ }
911
+ function addMapping(solLineIdx, tsLine) {
912
+ if (tsLine !== undefined && solLineIdx >= 0) {
913
+ mappings[solLineIdx + 1] = tsLine; // convert to 1-based
914
+ }
915
+ }
916
+ /**
917
+ * Map function/constructor body statements to Solidity lines.
918
+ * Walks statements in order, using generateStatement to count lines
919
+ * for each statement so we know exactly where each one appears.
920
+ */
921
+ function mapBodyStatements(body, startLineIdx, indent) {
922
+ let currentIdx = startLineIdx;
923
+ for (const stmt of body) {
924
+ addMapping(currentIdx, stmt.sourceLine);
925
+ const stmtText = generateStatement(stmt, indent);
926
+ const stmtLineCount = stmtText.split("\n").length;
927
+ // Recurse into compound statement bodies
928
+ if (stmt.kind === "if" && !isRequirePattern(stmt)) {
929
+ // Line 0: if (cond) {
930
+ mapBodyStatements(stmt.thenBody, currentIdx + 1, indent + " ");
931
+ if (stmt.elseBody) {
932
+ const thenLineCount = stmt.thenBody.reduce((sum, s) => sum + generateStatement(s, indent + " ").split("\n").length, 0);
933
+ // } else { is at currentIdx + 1 + thenLineCount
934
+ mapBodyStatements(stmt.elseBody, currentIdx + 1 + thenLineCount + 1, indent + " ");
935
+ }
936
+ }
937
+ else if (stmt.kind === "expression" && stmt.expression.kind === "conditional" && indent) {
938
+ // Lowered void ternary: map all generated lines to the original source line
939
+ for (let i = 1; i < stmtLineCount; i++) {
940
+ addMapping(currentIdx + i, stmt.sourceLine);
941
+ }
942
+ }
943
+ else if (stmt.kind === "for" || stmt.kind === "while") {
944
+ mapBodyStatements(stmt.body, currentIdx + 1, indent + " ");
945
+ }
946
+ else if (stmt.kind === "do-while") {
947
+ mapBodyStatements(stmt.body, currentIdx + 1, indent + " ");
948
+ }
949
+ currentIdx += stmtLineCount;
950
+ }
951
+ }
952
+ for (const contract of contracts) {
953
+ // Find the contract declaration line
954
+ const contractIdx = findLine((l) => {
955
+ const trimmed = l.trimStart();
956
+ return trimmed.startsWith(`contract ${contract.name}`) || trimmed.startsWith(`abstract contract ${contract.name}`);
957
+ });
958
+ if (contractIdx === -1)
959
+ continue;
960
+ addMapping(contractIdx, contract.sourceLine);
961
+ lineIdx = contractIdx + 1;
962
+ // Map events
963
+ for (const e of contract.events) {
964
+ const idx = findLine((l) => {
965
+ const trimmed = l.trim();
966
+ return trimmed.startsWith(`event ${e.name}(`);
967
+ });
968
+ if (idx !== -1) {
969
+ addMapping(idx, e.sourceLine);
970
+ lineIdx = idx + 1;
971
+ }
972
+ }
973
+ // Map variables
974
+ for (const v of contract.variables) {
975
+ const idx = findLine((l) => {
976
+ const trimmed = l.trim();
977
+ return trimmed.includes(` ${v.name}`) && trimmed.endsWith(";");
978
+ });
979
+ if (idx !== -1) {
980
+ addMapping(idx, v.sourceLine);
981
+ lineIdx = idx + 1;
982
+ }
983
+ }
984
+ // Map constructor
985
+ if (contract.ctor) {
986
+ const ctorIdx = findLine((l) => l.trim().startsWith("constructor("));
987
+ if (ctorIdx !== -1) {
988
+ addMapping(ctorIdx, contract.ctor.sourceLine);
989
+ lineIdx = ctorIdx + 1;
990
+ mapBodyStatements(contract.ctor.body, lineIdx, " ");
991
+ }
992
+ }
993
+ // Map functions
994
+ for (const f of contract.functions) {
995
+ const funcIdx = findLine((l) => {
996
+ const trimmed = l.trim();
997
+ if (f.name === "receive")
998
+ return trimmed.startsWith("receive()");
999
+ if (f.name === "fallback")
1000
+ return trimmed.startsWith("fallback()");
1001
+ return trimmed.startsWith(`function ${f.name}(`);
1002
+ });
1003
+ if (funcIdx !== -1) {
1004
+ addMapping(funcIdx, f.sourceLine);
1005
+ lineIdx = funcIdx + 1;
1006
+ mapBodyStatements(f.body, lineIdx, " ");
1007
+ }
1008
+ }
1009
+ }
1010
+ return { sourceFile, mappings };
1011
+ }
628
1012
  //# sourceMappingURL=codegen.js.map