skittles 1.2.6 → 1.3.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.
Files changed (48) hide show
  1. package/README.md +4 -4
  2. package/dist/commands/clean.d.ts.map +1 -1
  3. package/dist/commands/clean.js +4 -1
  4. package/dist/commands/clean.js.map +1 -1
  5. package/dist/commands/compile.d.ts +1 -0
  6. package/dist/commands/compile.d.ts.map +1 -1
  7. package/dist/commands/compile.js +51 -1
  8. package/dist/commands/compile.js.map +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +5 -4
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/test.d.ts +7 -0
  13. package/dist/commands/test.d.ts.map +1 -0
  14. package/dist/commands/test.js +60 -0
  15. package/dist/commands/test.js.map +1 -0
  16. package/dist/compiler/analysis.d.ts +7 -0
  17. package/dist/compiler/analysis.d.ts.map +1 -0
  18. package/dist/compiler/analysis.js +234 -0
  19. package/dist/compiler/analysis.js.map +1 -0
  20. package/dist/compiler/codegen.d.ts +10 -1
  21. package/dist/compiler/codegen.d.ts.map +1 -1
  22. package/dist/compiler/codegen.js +389 -22
  23. package/dist/compiler/codegen.js.map +1 -1
  24. package/dist/compiler/compiler.d.ts +2 -1
  25. package/dist/compiler/compiler.d.ts.map +1 -1
  26. package/dist/compiler/compiler.js +94 -9
  27. package/dist/compiler/compiler.js.map +1 -1
  28. package/dist/compiler/parser.d.ts +7 -1
  29. package/dist/compiler/parser.d.ts.map +1 -1
  30. package/dist/compiler/parser.js +731 -47
  31. package/dist/compiler/parser.js.map +1 -1
  32. package/dist/config/config.d.ts.map +1 -1
  33. package/dist/config/config.js +5 -1
  34. package/dist/config/config.js.map +1 -1
  35. package/dist/exports.d.ts +13 -2
  36. package/dist/exports.d.ts.map +1 -1
  37. package/dist/exports.js.map +1 -1
  38. package/dist/index.js +19 -4
  39. package/dist/index.js.map +1 -1
  40. package/dist/testing.d.ts +93 -0
  41. package/dist/testing.d.ts.map +1 -0
  42. package/dist/testing.js +143 -0
  43. package/dist/testing.js.map +1 -0
  44. package/dist/types/index.d.ts +51 -2
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/types/index.js +1 -0
  47. package/dist/types/index.js.map +1 -1
  48. package/package.json +2 -2
@@ -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) {
193
+ parts.push("");
194
+ }
195
+ }
196
+ for (let i = 0; i < functionsToEmit.length; i++) {
197
+ parts.push(generateFunction(functionsToEmit[i]));
198
+ if (i < functionsToEmit.length - 1 || readonlyArrayVars.length > 0) {
119
199
  parts.push("");
120
200
  }
121
201
  }
122
- for (let i = 0; i < contract.functions.length; i++) {
123
- parts.push(generateFunction(contract.functions[i]));
124
- if (i < contract.functions.length - 1) {
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
  }
@@ -532,6 +698,26 @@ export function generateStatement(stmt, indent) {
532
698
  lines.push(`${indent}}`);
533
699
  return lines.join("\n");
534
700
  }
701
+ case "try-catch": {
702
+ const lines = [];
703
+ const callExpr = generateExpression(stmt.call);
704
+ let returns = "";
705
+ if (stmt.returnVarName && stmt.returnType && stmt.returnType.kind !== SkittlesTypeKind.Void) {
706
+ returns = ` returns (${generateType(stmt.returnType)} ${stmt.returnVarName})`;
707
+ }
708
+ lines.push(`${indent}try ${callExpr}${returns} {`);
709
+ for (const s of stmt.successBody) {
710
+ lines.push(generateStatement(s, inner));
711
+ }
712
+ lines.push(`${indent}} catch {`);
713
+ for (const s of stmt.catchBody) {
714
+ lines.push(generateStatement(s, inner));
715
+ }
716
+ lines.push(`${indent}}`);
717
+ return lines.join("\n");
718
+ }
719
+ case "console-log":
720
+ return `${indent}console.log(${stmt.args.map(generateExpression).join(", ")});`;
535
721
  default:
536
722
  return `${indent}// unsupported statement`;
537
723
  }
@@ -570,6 +756,16 @@ function negateOperator(op) {
570
756
  };
571
757
  return map[op] ?? null;
572
758
  }
759
+ /**
760
+ * Check if a receiver expression is `this` (an internal contract method call).
761
+ * Used to avoid wrapping `this.transfer(...)` calls in `payable(...)`.
762
+ * Note: `this.stateVar.transfer(amount)` is NOT excluded here because
763
+ * codegen strips `this.` and the arg count (1 for ETH transfer vs 2+ for
764
+ * contract interface calls) serves as the discriminator.
765
+ */
766
+ function isThisOrContractCall(receiver) {
767
+ return receiver.kind === "identifier" && receiver.name === "this";
768
+ }
573
769
  // ============================================================
574
770
  // Built-in function recognition
575
771
  // ============================================================
@@ -597,8 +793,13 @@ function tryGenerateBuiltinCall(expr) {
597
793
  return `abi.encode(${args})`;
598
794
  case "abi.encodePacked":
599
795
  return `abi.encodePacked(${args})`;
600
- case "abi.decode":
796
+ case "abi.decode": {
797
+ if (expr.typeArgs && expr.typeArgs.length > 0) {
798
+ const types = expr.typeArgs.map(generateType).join(", ");
799
+ return `abi.decode(${args}, (${types}))`;
800
+ }
601
801
  return `abi.decode(${args})`;
802
+ }
602
803
  case "ecrecover":
603
804
  return `ecrecover(${args})`;
604
805
  case "addmod":
@@ -609,6 +810,11 @@ function tryGenerateBuiltinCall(expr) {
609
810
  return `assert(${args})`;
610
811
  case "gasleft":
611
812
  return `gasleft()`;
813
+ case "Contract":
814
+ if (expr.typeArgs && expr.typeArgs.length > 0 && expr.typeArgs[0].kind === SkittlesTypeKind.ContractInterface && expr.typeArgs[0].structName) {
815
+ return `${expr.typeArgs[0].structName}(${args})`;
816
+ }
817
+ throw new Error("Contract<T>() requires a contract interface type argument, e.g. Contract<IToken>(address)");
612
818
  case "string.concat":
613
819
  return `string.concat(${args})`;
614
820
  case "bytes.concat":
@@ -618,6 +824,47 @@ function tryGenerateBuiltinCall(expr) {
618
824
  }
619
825
  }
620
826
  // ============================================================
827
+ // Console.log detection
828
+ // ============================================================
829
+ function statementsUseConsoleLog(stmts) {
830
+ for (const stmt of stmts) {
831
+ if (stmt.kind === "console-log")
832
+ return true;
833
+ if (stmt.kind === "if") {
834
+ if (statementsUseConsoleLog(stmt.thenBody))
835
+ return true;
836
+ if (stmt.elseBody && statementsUseConsoleLog(stmt.elseBody))
837
+ return true;
838
+ }
839
+ if (stmt.kind === "for" || stmt.kind === "while" || stmt.kind === "do-while") {
840
+ if (statementsUseConsoleLog(stmt.body))
841
+ return true;
842
+ }
843
+ if (stmt.kind === "switch") {
844
+ for (const c of stmt.cases) {
845
+ if (statementsUseConsoleLog(c.body))
846
+ return true;
847
+ }
848
+ }
849
+ if (stmt.kind === "try-catch") {
850
+ if (statementsUseConsoleLog(stmt.successBody))
851
+ return true;
852
+ if (statementsUseConsoleLog(stmt.catchBody))
853
+ return true;
854
+ }
855
+ }
856
+ return false;
857
+ }
858
+ function contractUsesConsoleLog(contract) {
859
+ for (const f of contract.functions) {
860
+ if (statementsUseConsoleLog(f.body))
861
+ return true;
862
+ }
863
+ if (contract.ctor && statementsUseConsoleLog(contract.ctor.body))
864
+ return true;
865
+ return false;
866
+ }
867
+ // ============================================================
621
868
  // Visibility mapping (Skittles simplification)
622
869
  // ============================================================
623
870
  function mapVisibility(vis) {
@@ -625,4 +872,124 @@ function mapVisibility(vis) {
625
872
  return "public";
626
873
  return "internal";
627
874
  }
875
+ // ============================================================
876
+ // Source map generation
877
+ // ============================================================
878
+ /**
879
+ * Build a source map that maps generated Solidity line numbers
880
+ * back to TypeScript source line numbers.
881
+ *
882
+ * The mapping is built by walking the IR (which has source line info
883
+ * from the parser) and counting lines in the generated Solidity output
884
+ * to correlate each Solidity line with its TypeScript origin.
885
+ */
886
+ export function buildSourceMap(solidity, contracts, sourceFile) {
887
+ const solLines = solidity.split("\n");
888
+ const mappings = {};
889
+ let lineIdx = 0; // 0-based index into solLines
890
+ // Helper: find the next line matching a test, starting from lineIdx
891
+ function findLine(test) {
892
+ for (let i = lineIdx; i < solLines.length; i++) {
893
+ if (test(solLines[i])) {
894
+ lineIdx = i;
895
+ return i;
896
+ }
897
+ }
898
+ return -1;
899
+ }
900
+ function addMapping(solLineIdx, tsLine) {
901
+ if (tsLine !== undefined && solLineIdx >= 0) {
902
+ mappings[solLineIdx + 1] = tsLine; // convert to 1-based
903
+ }
904
+ }
905
+ /**
906
+ * Map function/constructor body statements to Solidity lines.
907
+ * Walks statements in order, using generateStatement to count lines
908
+ * for each statement so we know exactly where each one appears.
909
+ */
910
+ function mapBodyStatements(body, startLineIdx, indent) {
911
+ let currentIdx = startLineIdx;
912
+ for (const stmt of body) {
913
+ addMapping(currentIdx, stmt.sourceLine);
914
+ const stmtText = generateStatement(stmt, indent);
915
+ const stmtLineCount = stmtText.split("\n").length;
916
+ // Recurse into compound statement bodies
917
+ if (stmt.kind === "if" && !isRequirePattern(stmt)) {
918
+ // Line 0: if (cond) {
919
+ mapBodyStatements(stmt.thenBody, currentIdx + 1, indent + " ");
920
+ if (stmt.elseBody) {
921
+ const thenLineCount = stmt.thenBody.reduce((sum, s) => sum + generateStatement(s, indent + " ").split("\n").length, 0);
922
+ // } else { is at currentIdx + 1 + thenLineCount
923
+ mapBodyStatements(stmt.elseBody, currentIdx + 1 + thenLineCount + 1, indent + " ");
924
+ }
925
+ }
926
+ else if (stmt.kind === "for" || stmt.kind === "while") {
927
+ mapBodyStatements(stmt.body, currentIdx + 1, indent + " ");
928
+ }
929
+ else if (stmt.kind === "do-while") {
930
+ mapBodyStatements(stmt.body, currentIdx + 1, indent + " ");
931
+ }
932
+ currentIdx += stmtLineCount;
933
+ }
934
+ }
935
+ for (const contract of contracts) {
936
+ // Find the contract declaration line
937
+ const contractIdx = findLine((l) => {
938
+ const trimmed = l.trimStart();
939
+ return trimmed.startsWith(`contract ${contract.name}`) || trimmed.startsWith(`abstract contract ${contract.name}`);
940
+ });
941
+ if (contractIdx === -1)
942
+ continue;
943
+ addMapping(contractIdx, contract.sourceLine);
944
+ lineIdx = contractIdx + 1;
945
+ // Map events
946
+ for (const e of contract.events) {
947
+ const idx = findLine((l) => {
948
+ const trimmed = l.trim();
949
+ return trimmed.startsWith(`event ${e.name}(`);
950
+ });
951
+ if (idx !== -1) {
952
+ addMapping(idx, e.sourceLine);
953
+ lineIdx = idx + 1;
954
+ }
955
+ }
956
+ // Map variables
957
+ for (const v of contract.variables) {
958
+ const idx = findLine((l) => {
959
+ const trimmed = l.trim();
960
+ return trimmed.includes(` ${v.name}`) && trimmed.endsWith(";");
961
+ });
962
+ if (idx !== -1) {
963
+ addMapping(idx, v.sourceLine);
964
+ lineIdx = idx + 1;
965
+ }
966
+ }
967
+ // Map constructor
968
+ if (contract.ctor) {
969
+ const ctorIdx = findLine((l) => l.trim().startsWith("constructor("));
970
+ if (ctorIdx !== -1) {
971
+ addMapping(ctorIdx, contract.ctor.sourceLine);
972
+ lineIdx = ctorIdx + 1;
973
+ mapBodyStatements(contract.ctor.body, lineIdx, " ");
974
+ }
975
+ }
976
+ // Map functions
977
+ for (const f of contract.functions) {
978
+ const funcIdx = findLine((l) => {
979
+ const trimmed = l.trim();
980
+ if (f.name === "receive")
981
+ return trimmed.startsWith("receive()");
982
+ if (f.name === "fallback")
983
+ return trimmed.startsWith("fallback()");
984
+ return trimmed.startsWith(`function ${f.name}(`);
985
+ });
986
+ if (funcIdx !== -1) {
987
+ addMapping(funcIdx, f.sourceLine);
988
+ lineIdx = funcIdx + 1;
989
+ mapBodyStatements(f.body, lineIdx, " ");
990
+ }
991
+ }
992
+ }
993
+ return { sourceFile, mappings };
994
+ }
628
995
  //# sourceMappingURL=codegen.js.map