@distrohelena/canton-explorer 0.1.5 → 0.1.7

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 (110) hide show
  1. package/README.md +32 -0
  2. package/dist/public/assets/DebuggerView-Bfz-S94A.js +5 -0
  3. package/dist/public/assets/abap-08VXUWAP.js +1 -0
  4. package/dist/public/assets/apex-BWPQTe0t.js +1 -0
  5. package/dist/public/assets/azcli-Bc_sGQ0U.js +1 -0
  6. package/dist/public/assets/bat-i0X4ZdIN.js +1 -0
  7. package/dist/public/assets/bicep-B5-_aFwp.js +2 -0
  8. package/dist/public/assets/cameligo-DMUM7wLl.js +1 -0
  9. package/dist/public/assets/clojure-Cm7r79vr.js +1 -0
  10. package/dist/public/assets/codicon-ngg6Pgfi.ttf +0 -0
  11. package/dist/public/assets/coffee-Ba7i2nA0.js +1 -0
  12. package/dist/public/assets/cpp-C7h46wYY.js +1 -0
  13. package/dist/public/assets/csharp-BKxtCVv1.js +1 -0
  14. package/dist/public/assets/csp-bTuwJoIa.js +1 -0
  15. package/dist/public/assets/css-DIMkf-bt.js +3 -0
  16. package/dist/public/assets/cssMode-Ce-rlOrP.js +1 -0
  17. package/dist/public/assets/cypher-CVaqCwHa.js +1 -0
  18. package/dist/public/assets/dart-onAF5SnQ.js +1 -0
  19. package/dist/public/assets/dockerfile-DZFCIeNp.js +1 -0
  20. package/dist/public/assets/ecl-D05T4iGw.js +1 -0
  21. package/dist/public/assets/editor-Br_kD0ds.css +1 -0
  22. package/dist/public/assets/editor.api2-7H_iQTr0.js +872 -0
  23. package/dist/public/assets/elixir-6RTg0lbw.js +1 -0
  24. package/dist/public/assets/flow9-C5_-GSwl.js +1 -0
  25. package/dist/public/assets/freemarker2-B-DvnnHs.js +3 -0
  26. package/dist/public/assets/fsharp-C8Ef5oNN.js +1 -0
  27. package/dist/public/assets/go-C-y9NEjX.js +1 -0
  28. package/dist/public/assets/graphql-fmXr3nnJ.js +1 -0
  29. package/dist/public/assets/handlebars-CME0Fldi.js +1 -0
  30. package/dist/public/assets/hcl-CpzslTdj.js +1 -0
  31. package/dist/public/assets/html-Beb3lxWO.js +1 -0
  32. package/dist/public/assets/htmlMode-CAgp27Dj.js +1 -0
  33. package/dist/public/assets/index-CucRixYI.css +1 -0
  34. package/dist/public/assets/{index-BmT5Hrud.js → index-x-0ez1Y2.js} +1 -1
  35. package/dist/public/assets/ini-sBoK_t0W.js +1 -0
  36. package/dist/public/assets/java-BEtHBSE6.js +1 -0
  37. package/dist/public/assets/javascript-DdcWrUzt.js +1 -0
  38. package/dist/public/assets/jsonMode-BCddWWWg.js +7 -0
  39. package/dist/public/assets/julia-Bri6UV-V.js +1 -0
  40. package/dist/public/assets/kotlin-BOotOW0E.js +1 -0
  41. package/dist/public/assets/less-B9JPFI3C.js +2 -0
  42. package/dist/public/assets/lexon-CfSJPG6W.js +1 -0
  43. package/dist/public/assets/liquid-DmF9SgC3.js +1 -0
  44. package/dist/public/assets/lspLanguageFeatures-D7zEWtOG.js +4 -0
  45. package/dist/public/assets/lua-CsQS60Ue.js +1 -0
  46. package/dist/public/assets/m3-D-oSqn_W.js +1 -0
  47. package/dist/public/assets/markdown-Cimd5fb3.js +1 -0
  48. package/dist/public/assets/mdx-PwCfoB0S.js +1 -0
  49. package/dist/public/assets/mips-CIPQ_RoX.js +1 -0
  50. package/dist/public/assets/monaco-runtime-BnrEG_00.js +6 -0
  51. package/dist/public/assets/monaco.contribution-CeLwSWQP.js +2 -0
  52. package/dist/public/assets/msdax-DauUninz.js +1 -0
  53. package/dist/public/assets/mysql-SOo6toE5.js +1 -0
  54. package/dist/public/assets/objective-c-FvmIjYaQ.js +1 -0
  55. package/dist/public/assets/pascal-DrH0SRf2.js +1 -0
  56. package/dist/public/assets/pascaligo-D-ptJ9y-.js +1 -0
  57. package/dist/public/assets/perl-oz_6vUea.js +1 -0
  58. package/dist/public/assets/pgsql-DTj74zXo.js +1 -0
  59. package/dist/public/assets/php-nr791fC2.js +1 -0
  60. package/dist/public/assets/pla-CopQ2nXW.js +1 -0
  61. package/dist/public/assets/postiats-43DmfD33.js +1 -0
  62. package/dist/public/assets/powerquery-D3hlyOfw.js +1 -0
  63. package/dist/public/assets/powershell-DmHpPYUd.js +1 -0
  64. package/dist/public/assets/protobuf-C531GsRP.js +2 -0
  65. package/dist/public/assets/pug-Z5eAx3Zn.js +1 -0
  66. package/dist/public/assets/python-pS1EqvyQ.js +1 -0
  67. package/dist/public/assets/qsharp-DkqhCAOL.js +1 -0
  68. package/dist/public/assets/r-BwWrilGY.js +1 -0
  69. package/dist/public/assets/razor-CZ2fK_eC.js +1 -0
  70. package/dist/public/assets/redis-ClamHrr6.js +1 -0
  71. package/dist/public/assets/redshift-DT7zqm-g.js +1 -0
  72. package/dist/public/assets/restructuredtext-BYgofb2h.js +1 -0
  73. package/dist/public/assets/ruby-DezsRK8O.js +1 -0
  74. package/dist/public/assets/rust-DdL9SqIa.js +1 -0
  75. package/dist/public/assets/sb-CcwsVR0C.js +1 -0
  76. package/dist/public/assets/scala-DHpiXF5c.js +1 -0
  77. package/dist/public/assets/scheme-BeGwcela.js +1 -0
  78. package/dist/public/assets/scss-gp-XZpBa.js +3 -0
  79. package/dist/public/assets/shell-CC2rA5mh.js +1 -0
  80. package/dist/public/assets/solidity-BEEn4gHE.js +1 -0
  81. package/dist/public/assets/sophia-CRfGWb83.js +1 -0
  82. package/dist/public/assets/sparql-D_Lu-MrJ.js +1 -0
  83. package/dist/public/assets/sql-NEE52Syq.js +1 -0
  84. package/dist/public/assets/st-DbInun42.js +1 -0
  85. package/dist/public/assets/swift-Bxkupp3x.js +1 -0
  86. package/dist/public/assets/systemverilog-Bz4Y3fRF.js +1 -0
  87. package/dist/public/assets/tcl-DISqw1ZD.js +1 -0
  88. package/dist/public/assets/tsMode-ib37kZCj.js +11 -0
  89. package/dist/public/assets/twig-De2hgUGE.js +1 -0
  90. package/dist/public/assets/typescript-DS7X1pib.js +1 -0
  91. package/dist/public/assets/typespec-B8J7ngcE.js +1 -0
  92. package/dist/public/assets/vb-DV3o63ZY.js +1 -0
  93. package/dist/public/assets/wgsl-DpFanUEy.js +298 -0
  94. package/dist/public/assets/workers-D4g7l69J.js +1 -0
  95. package/dist/public/assets/xml-Br0rHxWi.js +1 -0
  96. package/dist/public/assets/yaml-qASkMRyN.js +1 -0
  97. package/dist/public/index.html +2 -2
  98. package/dist/src/debugger/debugger.service.d.ts +2 -0
  99. package/dist/src/debugger/debugger.service.js +2 -0
  100. package/dist/src/debugger/debugger.service.js.map +1 -1
  101. package/dist/tsconfig.build.tsbuildinfo +1 -1
  102. package/package.json +10 -6
  103. package/scripts/build-debug-dar.mjs +277 -0
  104. package/scripts/dar-archive-entries.mjs +10 -0
  105. package/scripts/generate-daml-source-map.mjs +1505 -0
  106. package/scripts/prepare-debug-dars-from-workspace.mjs +181 -0
  107. package/dist/public/assets/DebuggerView-VieEr0b3.js +0 -2
  108. package/dist/public/assets/index-DnEheBxd.css +0 -1
  109. package/dist/public/assets/monaco-runtime-C5uazxST.css +0 -1
  110. package/dist/public/assets/monaco-runtime-DiIHw4v2.js +0 -820
@@ -0,0 +1,1505 @@
1
+ import { readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { findDebugDalfEntry } from './dar-archive-entries.mjs'
4
+
5
+ async function main() {
6
+ const options = parseArgs(process.argv.slice(2))
7
+
8
+ if (options.help) {
9
+ printHelp()
10
+ return
11
+ }
12
+
13
+ validateOptions(options)
14
+
15
+ const sdk = await import('@distrohelena/canton-typescript-sdk/daml-lf')
16
+ const archiveLoader = new sdk.DarArchiveLoader()
17
+ const packageLoader = new sdk.DamlLfPackageLoader()
18
+ const archive = await archiveLoader.loadDarOrThrowAsync(readFileSync(options.inputDar))
19
+ const rawPackage = packageLoader.loadRawPackageOrThrow(archive.mainPackageEntry.bytes)
20
+ const compiledPackage = packageLoader.loadPackageOrThrow(archive.mainPackageEntry.bytes)
21
+ const debugDalfBytes = resolveDebugDalfBytes(options, archive)
22
+ const debugPackage = debugDalfBytes
23
+ ? packageLoader.loadPackageOrThrow(debugDalfBytes)
24
+ : undefined
25
+
26
+ const packageDirectory = resolvePackageDirectory(
27
+ options.workspaceRoot,
28
+ options.packageDir,
29
+ compiledPackage.packageName,
30
+ )
31
+ const damlYaml = parseDamlYaml(path.join(packageDirectory, 'daml.yaml'))
32
+ const sourceDirectory = path.resolve(packageDirectory, damlYaml.source)
33
+ const archiveSourcePrefix = normalizeArchivePath(options.sourcePrefix || damlYaml.source)
34
+ const sourceModules = collectSourceModules(
35
+ sourceDirectory,
36
+ archiveSourcePrefix,
37
+ options.workspaceRoot,
38
+ )
39
+ const compiledModules = compiledPackage.modules.map((module) => {
40
+ const sourceModule = sourceModules.get(module.name)
41
+ const definitions = module.definitions.filter((definition) => definition.nodeKind === 'valueDefinition')
42
+ return {
43
+ moduleName: module.name,
44
+ sourceModule,
45
+ definitions,
46
+ }
47
+ })
48
+
49
+ const executables = []
50
+ let exactMatches = 0
51
+ let fallbackMatches = 0
52
+ let missingModules = 0
53
+
54
+ for (const compiledModule of compiledModules) {
55
+ if (compiledModule.sourceModule === undefined) {
56
+ missingModules += 1
57
+ continue
58
+ }
59
+
60
+ for (const definition of compiledModule.definitions) {
61
+ const mapping = resolveDefinitionMapping(definition.name, compiledModule.sourceModule)
62
+
63
+ if (mapping.kind === 'exact') {
64
+ exactMatches += 1
65
+ } else {
66
+ fallbackMatches += 1
67
+ }
68
+
69
+ executables.push({
70
+ packageId: compiledPackage.packageId,
71
+ moduleName: compiledModule.moduleName,
72
+ definitionName: definition.name,
73
+ path: compiledModule.sourceModule.archivePath,
74
+ startLine: mapping.span.startLine,
75
+ startColumn: mapping.span.startColumn,
76
+ endLine: mapping.span.endLine,
77
+ endColumn: mapping.span.endColumn,
78
+ precision: mapping.kind,
79
+ ...(mapping.entrypointKind ? { entrypointKind: mapping.entrypointKind } : {}),
80
+ ...(mapping.templateName ? { templateName: mapping.templateName } : {}),
81
+ ...(mapping.choiceName ? { choiceName: mapping.choiceName } : {}),
82
+ ...(mapping.choiceArgumentFieldName
83
+ ? { choiceArgumentFieldName: mapping.choiceArgumentFieldName }
84
+ : {}),
85
+ })
86
+ }
87
+ }
88
+
89
+ const importedPackages = await collectImportedPackageIds(
90
+ rawPackage.rawPackage,
91
+ compiledPackage,
92
+ options.workspaceImportsOnly
93
+ ? await collectWorkspacePackageIds(options.workspaceRoot, options.packageDir)
94
+ : undefined,
95
+ )
96
+ const expressionLocationResult = debugPackage
97
+ ? collectExpressionLocations(compiledPackage, debugPackage, sourceModules)
98
+ : { locations: [], skippedDefinitions: [] }
99
+ const expressionLocations = expressionLocationResult.locations
100
+ const interfaceChoiceExecutables = collectInterfaceChoiceExecutables(
101
+ compiledPackage.packageId,
102
+ compiledModules,
103
+ executables,
104
+ expressionLocations,
105
+ )
106
+
107
+ const metadata = {
108
+ packageId: compiledPackage.packageId,
109
+ importedPackages,
110
+ executables: [...executables, ...interfaceChoiceExecutables],
111
+ expressionLocations,
112
+ }
113
+
114
+ writeFileSync(options.outputPath, JSON.stringify(metadata, null, 2))
115
+
116
+ console.log(`Wrote source map: ${options.outputPath}`)
117
+ console.log(`Package: ${compiledPackage.packageName}@${compiledPackage.packageVersion}`)
118
+ console.log(`Definitions mapped: ${executables.length}`)
119
+ console.log(`Exact matches: ${exactMatches}`)
120
+ console.log(`Module fallbacks: ${fallbackMatches}`)
121
+ if (interfaceChoiceExecutables.length > 0) {
122
+ console.log(`Interface choice executables: ${interfaceChoiceExecutables.length}`)
123
+ }
124
+ console.log(`Expression locations: ${expressionLocations.length}`)
125
+ if (expressionLocationResult.skippedDefinitions.length > 0) {
126
+ console.log(
127
+ `Skipped non-isomorphic debug definitions: ${expressionLocationResult.skippedDefinitions.length}`,
128
+ )
129
+ }
130
+ if (missingModules > 0) {
131
+ console.log(`Source modules not found: ${missingModules}`)
132
+ }
133
+ }
134
+
135
+ function parseArgs(argv) {
136
+ const options = {
137
+ help: false,
138
+ inputDar: '',
139
+ outputPath: '',
140
+ workspaceRoot: '',
141
+ packageDir: '',
142
+ sourcePrefix: '',
143
+ debugDalfPath: '',
144
+ workspaceImportsOnly: false,
145
+ }
146
+
147
+ for (let index = 0; index < argv.length; index += 1) {
148
+ const argument = argv[index]
149
+
150
+ switch (argument) {
151
+ case '--help':
152
+ case '-h':
153
+ options.help = true
154
+ break
155
+ case '--input':
156
+ options.inputDar = requireValue(argv, ++index, '--input')
157
+ break
158
+ case '--output':
159
+ options.outputPath = requireValue(argv, ++index, '--output')
160
+ break
161
+ case '--workspace-root':
162
+ options.workspaceRoot = requireValue(argv, ++index, '--workspace-root')
163
+ break
164
+ case '--package-dir':
165
+ options.packageDir = requireValue(argv, ++index, '--package-dir')
166
+ break
167
+ case '--source-prefix':
168
+ options.sourcePrefix = requireValue(argv, ++index, '--source-prefix')
169
+ break
170
+ case '--debug-dalf':
171
+ options.debugDalfPath = requireValue(argv, ++index, '--debug-dalf')
172
+ break
173
+ case '--workspace-imports-only':
174
+ options.workspaceImportsOnly = true
175
+ break
176
+ default:
177
+ throw new Error(`Unknown argument: ${argument}`)
178
+ }
179
+ }
180
+
181
+ if (!options.outputPath && options.inputDar) {
182
+ options.outputPath = defaultOutputPath(options.inputDar)
183
+ }
184
+
185
+ return options
186
+ }
187
+
188
+ function validateOptions(options) {
189
+ if (!options.inputDar) {
190
+ throw new Error('--input is required')
191
+ }
192
+ if (!options.workspaceRoot && !options.packageDir) {
193
+ throw new Error('--workspace-root is required unless --package-dir is provided')
194
+ }
195
+ if (!statExists(options.inputDar, 'file')) {
196
+ throw new Error(`Input DAR not found: ${options.inputDar}`)
197
+ }
198
+ if (options.workspaceRoot && !statExists(options.workspaceRoot, 'directory')) {
199
+ throw new Error(`Workspace root not found: ${options.workspaceRoot}`)
200
+ }
201
+ if (options.packageDir && !statExists(options.packageDir, 'directory')) {
202
+ throw new Error(`Package directory not found: ${options.packageDir}`)
203
+ }
204
+ if (options.debugDalfPath && !statExists(options.debugDalfPath, 'file')) {
205
+ throw new Error(`Debug DALF not found: ${options.debugDalfPath}`)
206
+ }
207
+ }
208
+
209
+ function resolveDebugDalfBytes(options, archive) {
210
+ if (options.debugDalfPath) {
211
+ return readFileSync(options.debugDalfPath)
212
+ }
213
+
214
+ const debugEntry = findDebugDalfEntry(archive)
215
+ return debugEntry?.bytes
216
+ }
217
+
218
+ function collectExpressionLocations(productionPackage, debugPackage, sourceModules) {
219
+ const locations = []
220
+ const skippedDefinitions = []
221
+ const debugModules = new Map(debugPackage.modules.map((module) => [module.name, module]))
222
+ const packageIdEquivalence = new Map([
223
+ [productionPackage.packageId, debugPackage.packageId],
224
+ [debugPackage.packageId, productionPackage.packageId],
225
+ ])
226
+
227
+ for (const productionModule of productionPackage.modules) {
228
+ const debugModule = debugModules.get(productionModule.name)
229
+
230
+ if (debugModule === undefined) {
231
+ throw new Error(`debug DALF is missing module ${productionModule.name}`)
232
+ }
233
+
234
+ const productionDefinitions = productionModule.definitions.filter(
235
+ (definition) => definition.nodeKind === 'valueDefinition',
236
+ )
237
+ const debugDefinitions = new Map(
238
+ debugModule.definitions
239
+ .filter((definition) => definition.nodeKind === 'valueDefinition')
240
+ .map((definition) => [definition.name, definition]),
241
+ )
242
+ for (const productionDefinition of productionDefinitions) {
243
+ const debugDefinition = debugDefinitions.get(productionDefinition.name)
244
+
245
+ if (debugDefinition === undefined) {
246
+ throw new Error(
247
+ `debug DALF is missing definition ${productionModule.name}:${productionDefinition.name}`,
248
+ )
249
+ }
250
+
251
+ if (
252
+ !structurallyEqualIgnoringSourceLocation(
253
+ productionDefinition.expression,
254
+ debugDefinition.expression,
255
+ packageIdEquivalence,
256
+ )
257
+ ) {
258
+ skippedDefinitions.push(`${productionModule.name}:${productionDefinition.name}`)
259
+ continue
260
+ }
261
+
262
+ collectExpressionLocationsFromExpression({
263
+ packageId: productionPackage.packageId,
264
+ moduleName: productionModule.name,
265
+ definitionName: productionDefinition.name,
266
+ productionExpression: productionDefinition.expression,
267
+ debugExpression: debugDefinition.expression,
268
+ expressionPath: [],
269
+ sourceModules,
270
+ packageIdEquivalence,
271
+ locations,
272
+ })
273
+ }
274
+ }
275
+
276
+ return { locations, skippedDefinitions }
277
+ }
278
+
279
+ function collectInterfaceChoiceExecutables(
280
+ packageId,
281
+ compiledModules,
282
+ executables,
283
+ expressionLocations,
284
+ ) {
285
+ const existingExerciseKeys = new Set(
286
+ executables
287
+ .filter((candidate) => candidate.entrypointKind === 'exercise')
288
+ .map((candidate) =>
289
+ `${candidate.packageId}::${candidate.moduleName}::${candidate.templateName}::${candidate.choiceName}`,
290
+ ),
291
+ )
292
+ const expressionLocationsByDefinition = new Map()
293
+
294
+ for (const location of expressionLocations) {
295
+ const key = `${location.moduleName}::${location.definitionName}`
296
+ const locations = expressionLocationsByDefinition.get(key) ?? []
297
+ locations.push(location)
298
+ expressionLocationsByDefinition.set(key, locations)
299
+ }
300
+
301
+ const resolvedExecutables = []
302
+
303
+ for (const compiledModule of compiledModules) {
304
+ const sourceModule = compiledModule.sourceModule
305
+ if (sourceModule === undefined) {
306
+ continue
307
+ }
308
+
309
+ for (const implementation of sourceModule.interfaceChoiceImplementations) {
310
+ const executableKey =
311
+ `${packageId}::${compiledModule.moduleName}::${implementation.templateName}::${implementation.choiceName}`
312
+ if (existingExerciseKeys.has(executableKey)) {
313
+ continue
314
+ }
315
+
316
+ const definitionName = resolveInterfaceChoiceDefinitionName(
317
+ compiledModule.moduleName,
318
+ compiledModule.definitions,
319
+ implementation.span,
320
+ implementation.templateName,
321
+ expressionLocationsByDefinition,
322
+ )
323
+
324
+ if (definitionName === undefined) {
325
+ continue
326
+ }
327
+
328
+ resolvedExecutables.push({
329
+ packageId,
330
+ moduleName: compiledModule.moduleName,
331
+ definitionName,
332
+ path: sourceModule.archivePath,
333
+ startLine: implementation.span.startLine,
334
+ startColumn: implementation.span.startColumn,
335
+ endLine: implementation.span.endLine,
336
+ endColumn: implementation.span.endColumn,
337
+ precision: 'exact',
338
+ entrypointKind: 'exercise',
339
+ templateName: implementation.templateName,
340
+ choiceName: implementation.choiceName,
341
+ choiceArgumentFieldName: implementation.choiceArgumentFieldName,
342
+ })
343
+ existingExerciseKeys.add(executableKey)
344
+ }
345
+ }
346
+
347
+ return resolvedExecutables
348
+ }
349
+
350
+ function resolveInterfaceChoiceDefinitionName(
351
+ moduleName,
352
+ definitions,
353
+ span,
354
+ templateName,
355
+ expressionLocationsByDefinition,
356
+ ) {
357
+ let bestCandidate
358
+
359
+ for (const definition of definitions) {
360
+ const key = `${moduleName}::${definition.name}`
361
+ const locations = expressionLocationsByDefinition.get(key)
362
+ if (locations === undefined || locations.length === 0) {
363
+ continue
364
+ }
365
+
366
+ const overlappingLines = new Set()
367
+ for (const location of locations) {
368
+ if (
369
+ location.startLine >= span.startLine - 1
370
+ && location.startLine <= span.endLine
371
+ ) {
372
+ overlappingLines.add(location.startLine)
373
+ }
374
+ }
375
+
376
+ if (overlappingLines.size === 0) {
377
+ continue
378
+ }
379
+
380
+ const candidate = {
381
+ definitionName: definition.name,
382
+ overlapScore: overlappingLines.size,
383
+ preferredTemplateScope:
384
+ definition.name.startsWith(`$$$$sc_${templateName}_`) ? 1 : 0,
385
+ }
386
+
387
+ if (
388
+ bestCandidate === undefined
389
+ || candidate.overlapScore > bestCandidate.overlapScore
390
+ || (
391
+ candidate.overlapScore === bestCandidate.overlapScore
392
+ && candidate.preferredTemplateScope > bestCandidate.preferredTemplateScope
393
+ )
394
+ ) {
395
+ bestCandidate = candidate
396
+ }
397
+ }
398
+
399
+ return bestCandidate?.definitionName
400
+ }
401
+
402
+ function collectExpressionLocationsFromExpression({
403
+ packageId,
404
+ moduleName,
405
+ definitionName,
406
+ productionExpression,
407
+ debugExpression,
408
+ expressionPath,
409
+ sourceModules,
410
+ packageIdEquivalence,
411
+ locations,
412
+ }) {
413
+ if (
414
+ !structurallyEqualIgnoringSourceLocation(
415
+ productionExpression,
416
+ debugExpression,
417
+ packageIdEquivalence,
418
+ )
419
+ ) {
420
+ throw new Error(
421
+ `debug DALF expression mismatch at ${moduleName}:${definitionName} path ${expressionPath.join('.')}`,
422
+ )
423
+ }
424
+
425
+ const sourceLocation = debugExpression.sourceLocation
426
+ if (sourceLocation !== undefined) {
427
+ const sourceModule = sourceModules.get(sourceLocation.moduleName ?? moduleName)
428
+
429
+ if (sourceModule !== undefined) {
430
+ locations.push({
431
+ packageId,
432
+ moduleName,
433
+ definitionName,
434
+ expressionPath,
435
+ path: sourceModule.archivePath,
436
+ startLine: sourceLocation.startLine,
437
+ startColumn: sourceLocation.startColumn,
438
+ endLine: sourceLocation.endLine,
439
+ endColumn: sourceLocation.endColumn,
440
+ })
441
+ }
442
+ }
443
+
444
+ const productionChildren = getExpressionChildren(productionExpression)
445
+ const debugChildren = getExpressionChildren(debugExpression)
446
+
447
+ if (productionChildren.length !== debugChildren.length) {
448
+ throw new Error(
449
+ `debug DALF child count mismatch at ${moduleName}:${definitionName} path ${expressionPath.join('.')}`,
450
+ )
451
+ }
452
+
453
+ for (let index = 0; index < productionChildren.length; index += 1) {
454
+ collectExpressionLocationsFromExpression({
455
+ packageId,
456
+ moduleName,
457
+ definitionName,
458
+ productionExpression: productionChildren[index],
459
+ debugExpression: debugChildren[index],
460
+ expressionPath: [...expressionPath, index],
461
+ sourceModules,
462
+ packageIdEquivalence,
463
+ locations,
464
+ })
465
+ }
466
+ }
467
+
468
+ function structurallyEqualIgnoringSourceLocation(left, right, packageIdEquivalence, parentKey = '') {
469
+ if (left === right) {
470
+ return true
471
+ }
472
+
473
+ if (
474
+ parentKey === 'packageId' &&
475
+ typeof left === 'string' &&
476
+ typeof right === 'string' &&
477
+ packageIdEquivalence.get(left) === right
478
+ ) {
479
+ return true
480
+ }
481
+
482
+ if (Array.isArray(left) || Array.isArray(right)) {
483
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
484
+ return false
485
+ }
486
+ return left.every((item, index) =>
487
+ structurallyEqualIgnoringSourceLocation(item, right[index], packageIdEquivalence, parentKey),
488
+ )
489
+ }
490
+
491
+ if (left && right && typeof left === 'object' && typeof right === 'object') {
492
+ const leftKeys = Object.keys(left).filter((key) => key !== 'sourceLocation')
493
+ const rightKeys = Object.keys(right).filter((key) => key !== 'sourceLocation')
494
+
495
+ if (leftKeys.length !== rightKeys.length) {
496
+ return false
497
+ }
498
+
499
+ for (let index = 0; index < leftKeys.length; index += 1) {
500
+ const key = leftKeys[index]
501
+
502
+ if (key !== rightKeys[index]) {
503
+ return false
504
+ }
505
+
506
+ if (
507
+ !structurallyEqualIgnoringSourceLocation(
508
+ left[key],
509
+ right[key],
510
+ packageIdEquivalence,
511
+ key,
512
+ )
513
+ ) {
514
+ return false
515
+ }
516
+ }
517
+
518
+ return true
519
+ }
520
+
521
+ return false
522
+ }
523
+
524
+ function getExpressionChildren(expression) {
525
+ if (expression.lambda !== undefined) {
526
+ return [expression.lambda.body]
527
+ }
528
+
529
+ if (expression.application !== undefined) {
530
+ return [
531
+ expression.application.function,
532
+ ...expression.application.arguments,
533
+ ]
534
+ }
535
+
536
+ if (expression.letExpression !== undefined) {
537
+ return [
538
+ ...expression.letExpression.bindings.map((binding) => binding.value),
539
+ expression.letExpression.body,
540
+ ]
541
+ }
542
+
543
+ if (expression.recordConstruction !== undefined) {
544
+ return expression.recordConstruction.fields.map((field) => field.value)
545
+ }
546
+
547
+ if (expression.recordProjection !== undefined) {
548
+ return [expression.recordProjection.record]
549
+ }
550
+
551
+ if (expression.recordUpdate !== undefined) {
552
+ return [
553
+ expression.recordUpdate.record,
554
+ expression.recordUpdate.value,
555
+ ]
556
+ }
557
+
558
+ if (expression.caseExpression !== undefined) {
559
+ return [
560
+ expression.caseExpression.scrutinee,
561
+ ...expression.caseExpression.alternatives.map((alternative) => alternative.body),
562
+ ]
563
+ }
564
+
565
+ if (expression.variantConstruction !== undefined) {
566
+ return [expression.variantConstruction.argument]
567
+ }
568
+
569
+ if (expression.optionalConstruction?.value !== undefined) {
570
+ return [expression.optionalConstruction.value]
571
+ }
572
+
573
+ if (expression.listConstruction !== undefined) {
574
+ return [
575
+ ...expression.listConstruction.front,
576
+ ...(expression.listConstruction.tail === undefined
577
+ ? []
578
+ : [expression.listConstruction.tail]),
579
+ ]
580
+ }
581
+
582
+ if (expression.updateExpression !== undefined) {
583
+ return [
584
+ ...(expression.updateExpression.expression === undefined
585
+ ? []
586
+ : [expression.updateExpression.expression]),
587
+ ...(expression.updateExpression.bindings?.map((binding) => binding.value) ?? []),
588
+ ...(expression.updateExpression.body === undefined
589
+ ? []
590
+ : [expression.updateExpression.body]),
591
+ ...(expression.updateExpression.contractId === undefined
592
+ ? []
593
+ : [expression.updateExpression.contractId]),
594
+ ...(expression.updateExpression.argument === undefined
595
+ ? []
596
+ : [expression.updateExpression.argument]),
597
+ ]
598
+ }
599
+
600
+ return []
601
+ }
602
+
603
+ function resolveDefinitionMapping(definitionName, sourceModule) {
604
+ const exactValue = sourceModule.values.get(definitionName)
605
+ if (exactValue !== undefined) {
606
+ return {
607
+ kind: 'exact',
608
+ span: exactValue,
609
+ }
610
+ }
611
+
612
+ const recursiveStrip = stripScopeWrapper(definitionName)
613
+ if (recursiveStrip !== definitionName) {
614
+ const nested = resolveDefinitionMapping(recursiveStrip, sourceModule)
615
+ return nested.kind === 'exact'
616
+ ? nested
617
+ : {
618
+ kind: 'fallback',
619
+ span: nested.span,
620
+ entrypointKind: nested.entrypointKind,
621
+ templateName: nested.templateName,
622
+ choiceName: nested.choiceName,
623
+ }
624
+ }
625
+
626
+ const decodedName = decodeGeneratedName(definitionName)
627
+
628
+ const fieldSpan = resolveFieldMapping(decodedName, sourceModule)
629
+ if (fieldSpan !== undefined) {
630
+ return {
631
+ kind: 'exact',
632
+ span: fieldSpan,
633
+ }
634
+ }
635
+
636
+ const generatedEntity = resolveGeneratedEntityMapping(definitionName, decodedName, sourceModule)
637
+ if (generatedEntity !== undefined) {
638
+ return generatedEntity
639
+ }
640
+
641
+ const ordinalTemplateSpan = resolveOrdinalTemplateFamily(decodedName, sourceModule)
642
+ if (ordinalTemplateSpan !== undefined) {
643
+ return {
644
+ kind: 'fallback',
645
+ span: ordinalTemplateSpan,
646
+ }
647
+ }
648
+
649
+ const ordinalTypeSpan = resolveOrdinalTypeFamily(decodedName, sourceModule)
650
+ if (ordinalTypeSpan !== undefined) {
651
+ return {
652
+ kind: 'fallback',
653
+ span: ordinalTypeSpan,
654
+ }
655
+ }
656
+
657
+ return {
658
+ kind: 'fallback',
659
+ span: sourceModule.moduleSpan,
660
+ }
661
+ }
662
+
663
+ function resolveGeneratedEntityMapping(definitionName, decodedName, sourceModule) {
664
+ const createMatch = matchTemplateSuffix(definitionName, sourceModule.templates, ['$$fHasCreate'])
665
+ if (createMatch !== undefined) {
666
+ return {
667
+ kind: 'exact',
668
+ span: createMatch.span,
669
+ entrypointKind: 'create',
670
+ templateName: createMatch.name,
671
+ }
672
+ }
673
+
674
+ const templatePrefixes = [
675
+ '$$fHasFetch',
676
+ '$$fHasArchive',
677
+ '$$fHasObserver',
678
+ '$$fHasSignatory',
679
+ '$$fHasEnsure',
680
+ '$$fHasIsInterfaceType',
681
+ '$$fHasTemplateTypeRep',
682
+ '$$fHasToAnyTemplate',
683
+ '$$fHasFromAnyTemplate',
684
+ '$$fHasToInterface',
685
+ '$$fHasFromInterface',
686
+ '$$W',
687
+ '$$fShow',
688
+ '$$fEq',
689
+ ]
690
+
691
+ const templateMatch = matchTemplateSuffix(definitionName, sourceModule.templates, templatePrefixes)
692
+ if (templateMatch !== undefined) {
693
+ return {
694
+ kind: 'exact',
695
+ span: templateMatch.span,
696
+ }
697
+ }
698
+
699
+ const typePrefixes = ['$$W', '$$fShow', '$$fEq']
700
+ const dataTypeMatch = matchTemplateSuffix(definitionName, sourceModule.dataTypes, typePrefixes)
701
+ if (dataTypeMatch !== undefined) {
702
+ return {
703
+ kind: 'exact',
704
+ span: dataTypeMatch.span,
705
+ }
706
+ }
707
+
708
+ const choiceMatch = matchChoiceSuffix(definitionName, sourceModule, [
709
+ '$$fHasExercise',
710
+ '$$fHasToAnyChoice',
711
+ '$$fHasFromAnyChoice',
712
+ ])
713
+ if (choiceMatch !== undefined) {
714
+ return choiceMatch
715
+ }
716
+
717
+ const decodedChoiceMatch = matchChoiceSuffix(decodedName, sourceModule, [
718
+ '$$fHasExercise',
719
+ '$$fHasToAnyChoice',
720
+ '$$fHasFromAnyChoice',
721
+ ])
722
+ if (decodedChoiceMatch !== undefined) {
723
+ return decodedChoiceMatch
724
+ }
725
+
726
+ if (definitionName.startsWith('$$fHasExercise') && definitionName.endsWith('Archive$u0028$u0029')) {
727
+ const template = matchTemplatePrefix(
728
+ definitionName.slice('$$fHasExercise'.length, -'Archive$u0028$u0029'.length),
729
+ sourceModule.templates,
730
+ )
731
+ if (template !== undefined) {
732
+ return {
733
+ kind: 'exact',
734
+ span: template.span,
735
+ entrypointKind: 'exercise',
736
+ templateName: template.name,
737
+ choiceName: 'Archive',
738
+ }
739
+ }
740
+ }
741
+
742
+ if (definitionName.startsWith('$$fHasExercise') && definitionName.endsWith('Archive()')) {
743
+ const template = matchTemplatePrefix(
744
+ definitionName.slice('$$fHasExercise'.length, -'Archive()'.length),
745
+ sourceModule.templates,
746
+ )
747
+ if (template !== undefined) {
748
+ return {
749
+ kind: 'exact',
750
+ span: template.span,
751
+ entrypointKind: 'exercise',
752
+ templateName: template.name,
753
+ choiceName: 'Archive',
754
+ }
755
+ }
756
+ }
757
+
758
+ return undefined
759
+ }
760
+
761
+ function resolveFieldMapping(decodedName, sourceModule) {
762
+ const selectorMatch = /^\$\$sel:([^:]+):(.+)$/.exec(decodedName)
763
+ if (selectorMatch) {
764
+ const fieldName = selectorMatch[1]
765
+ const typeName = selectorMatch[2]
766
+ const entity = sourceModule.templateOrDataEntities.find((candidate) => candidate.name === typeName)
767
+ return entity?.fields.get(fieldName)
768
+ }
769
+
770
+ const accessorMatch = /^\$\$f(?:Get|Set)Field"([^"]+)"(.+)$/.exec(decodedName)
771
+ if (accessorMatch) {
772
+ const fieldName = accessorMatch[1]
773
+ const typeMatch = matchTemplatePrefix(accessorMatch[2], sourceModule.templateOrDataEntityMap)
774
+ return typeMatch?.fields.get(fieldName)
775
+ }
776
+
777
+ return undefined
778
+ }
779
+
780
+ function resolveOrdinalTemplateFamily(decodedName, sourceModule) {
781
+ const match = /^\$\$c(?:archive|observer|signatory|ensure)(\d*)$/.exec(decodedName)
782
+ if (!match) {
783
+ return undefined
784
+ }
785
+ const index = match[1] === '' ? 0 : Number.parseInt(match[1], 10)
786
+ return sourceModule.templateOrder[index]?.span
787
+ }
788
+
789
+ function resolveOrdinalTypeFamily(decodedName, sourceModule) {
790
+ const match = /^\$\$c(?:show|showList|showsPrec|==|\/=)(\d*)$/.exec(decodedName)
791
+ if (!match) {
792
+ return undefined
793
+ }
794
+ const index = match[1] === '' ? 0 : Number.parseInt(match[1], 10)
795
+ return sourceModule.typeOrder[index]?.span
796
+ }
797
+
798
+ function matchTemplateSuffix(definitionName, entities, prefixes) {
799
+ for (const prefix of prefixes) {
800
+ if (!definitionName.startsWith(prefix)) {
801
+ continue
802
+ }
803
+
804
+ const suffix = definitionName.slice(prefix.length)
805
+ const entity = matchTemplatePrefix(suffix, entities)
806
+ if (entity !== undefined) {
807
+ return entity
808
+ }
809
+ }
810
+
811
+ return undefined
812
+ }
813
+
814
+ function matchChoiceSuffix(definitionName, sourceModule, prefixes) {
815
+ for (const prefix of prefixes) {
816
+ if (!definitionName.startsWith(prefix)) {
817
+ continue
818
+ }
819
+
820
+ const suffix = definitionName.slice(prefix.length)
821
+
822
+ for (const template of sourceModule.templateOrder) {
823
+ if (!suffix.startsWith(template.name)) {
824
+ continue
825
+ }
826
+
827
+ const afterTemplate = suffix.slice(template.name.length)
828
+ const choice = matchTemplatePrefix(afterTemplate, template.choices)
829
+
830
+ if (choice !== undefined) {
831
+ return {
832
+ kind: 'exact',
833
+ span: choice.span,
834
+ entrypointKind: prefix === '$$fHasExercise' ? 'exercise' : undefined,
835
+ templateName: prefix === '$$fHasExercise' ? template.name : undefined,
836
+ choiceName: prefix === '$$fHasExercise' ? choice.name : undefined,
837
+ }
838
+ }
839
+ }
840
+ }
841
+
842
+ return undefined
843
+ }
844
+
845
+ function matchTemplatePrefix(value, entities) {
846
+ let match
847
+
848
+ for (const entity of valuesOfEntityCollection(entities)) {
849
+ if (!value.startsWith(entity.name)) {
850
+ continue
851
+ }
852
+ if (match === undefined || entity.name.length > match.name.length) {
853
+ match = entity
854
+ }
855
+ }
856
+
857
+ return match
858
+ }
859
+
860
+ function valuesOfEntityCollection(entities) {
861
+ if (entities instanceof Map) {
862
+ return [...entities.values()]
863
+ }
864
+ return entities
865
+ }
866
+
867
+ function stripScopeWrapper(definitionName) {
868
+ const match = /^\$\$\$\$sc_(.+)_\d+$/.exec(definitionName)
869
+ return match?.[1] ?? definitionName
870
+ }
871
+
872
+ function decodeGeneratedName(value) {
873
+ return value.replace(/\$u([0-9a-fA-F]{4})/g, (_match, hex) =>
874
+ String.fromCharCode(Number.parseInt(hex, 16)),
875
+ )
876
+ }
877
+
878
+ function resolvePackageDirectory(workspaceRoot, packageDir, packageName) {
879
+ if (packageDir) {
880
+ return path.resolve(packageDir)
881
+ }
882
+
883
+ const matches = []
884
+ walkDirectories(path.resolve(workspaceRoot), (directoryPath) => {
885
+ const damlYamlPath = path.join(directoryPath, 'daml.yaml')
886
+ if (!statExists(damlYamlPath, 'file')) {
887
+ return
888
+ }
889
+ const parsed = parseDamlYaml(damlYamlPath)
890
+ if (parsed.name === packageName) {
891
+ matches.push(directoryPath)
892
+ }
893
+ })
894
+
895
+ if (matches.length === 0) {
896
+ throw new Error(`Could not find package '${packageName}' under ${workspaceRoot}`)
897
+ }
898
+
899
+ if (matches.length > 1) {
900
+ throw new Error(`Found multiple package directories for '${packageName}': ${matches.join(', ')}`)
901
+ }
902
+
903
+ return matches[0]
904
+ }
905
+
906
+ function parseDamlYaml(filePath) {
907
+ const text = readFileSync(filePath, 'utf8')
908
+ const result = {
909
+ name: '',
910
+ source: 'daml',
911
+ }
912
+
913
+ for (const line of text.split(/\r?\n/)) {
914
+ const match = /^([A-Za-z0-9_-]+)\s*:\s*(.+)\s*$/.exec(line.trim())
915
+ if (!match) {
916
+ continue
917
+ }
918
+ const key = match[1]
919
+ const value = stripYamlQuotes(match[2])
920
+
921
+ if (key === 'name') {
922
+ result.name = value
923
+ } else if (key === 'source') {
924
+ result.source = value
925
+ }
926
+ }
927
+
928
+ if (!result.name) {
929
+ throw new Error(`Could not read package name from ${filePath}`)
930
+ }
931
+
932
+ return result
933
+ }
934
+
935
+ async function collectImportedPackageIds(rawPackage, compiledPackage, allowedPackageIds) {
936
+ const directImports =
937
+ rawPackage.importsSum?.oneofKind === 'packageImports'
938
+ ? [...rawPackage.importsSum.packageImports.importedPackages]
939
+ : []
940
+
941
+ if (directImports.length > 0) {
942
+ return filterImportedPackages(directImports, allowedPackageIds)
943
+ }
944
+
945
+ const discoveredImports = new Set()
946
+ const serializedPackage = JSON.stringify(compiledPackage)
947
+ for (const match of serializedPackage.matchAll(/[0-9a-f]{64}/g)) {
948
+ discoveredImports.add(match[0])
949
+ }
950
+ discoveredImports.delete(compiledPackage.packageId)
951
+ return filterImportedPackages([...discoveredImports], allowedPackageIds)
952
+ }
953
+
954
+ async function collectWorkspacePackageIds(workspaceRoot, packageDir) {
955
+ const sdk = await import('@distrohelena/canton-typescript-sdk/daml-lf')
956
+ const archiveLoader = new sdk.DarArchiveLoader()
957
+ const packageLoader = new sdk.DamlLfPackageLoader()
958
+ const packageIds = new Set()
959
+ const roots = packageDir ? [path.resolve(packageDir)] : [path.resolve(workspaceRoot)]
960
+
961
+ for (const root of roots) {
962
+ for (const darPath of findDarFiles(root)) {
963
+ if (darPath.endsWith('-debug.dar')) {
964
+ continue
965
+ }
966
+
967
+ try {
968
+ const archive = await archiveLoader.loadDarOrThrowAsync(readFileSync(darPath))
969
+ const compiledPackage = packageLoader.loadPackageOrThrow(archive.mainPackageEntry.bytes)
970
+ packageIds.add(compiledPackage.packageId)
971
+ } catch {
972
+ // Ignore artifacts that are not package DARs we can index here.
973
+ }
974
+ }
975
+ }
976
+
977
+ return packageIds
978
+ }
979
+
980
+ function filterImportedPackages(importedPackages, allowedPackageIds) {
981
+ const uniquePackageIds = [...new Set(importedPackages)]
982
+ if (!allowedPackageIds || allowedPackageIds.size === 0) {
983
+ return uniquePackageIds.sort()
984
+ }
985
+
986
+ return uniquePackageIds
987
+ .filter((packageId) => allowedPackageIds.has(packageId))
988
+ .sort()
989
+ }
990
+
991
+ function stripYamlQuotes(value) {
992
+ if (
993
+ (value.startsWith('"') && value.endsWith('"'))
994
+ || (value.startsWith("'") && value.endsWith("'"))
995
+ ) {
996
+ return value.slice(1, -1)
997
+ }
998
+ return value
999
+ }
1000
+
1001
+ function collectSourceModules(sourceDirectory, archiveSourcePrefix, workspaceRoot) {
1002
+ const modules = new Map()
1003
+
1004
+ walkFiles(sourceDirectory, (filePath) => {
1005
+ if (!filePath.endsWith('.daml')) {
1006
+ return
1007
+ }
1008
+
1009
+ const relativePath = path.relative(sourceDirectory, filePath)
1010
+ const archivePath = joinArchivePath(archiveSourcePrefix, relativePath)
1011
+ const sourceModule = parseSourceModule(
1012
+ readFileSync(filePath, 'utf8'),
1013
+ archivePath,
1014
+ )
1015
+
1016
+ if (sourceModule !== undefined) {
1017
+ modules.set(sourceModule.moduleName, sourceModule)
1018
+ }
1019
+ })
1020
+
1021
+ resolveInterfaceChoiceImplementations(
1022
+ modules,
1023
+ workspaceRoot ? collectWorkspaceInterfaces(workspaceRoot) : undefined,
1024
+ )
1025
+
1026
+ return modules
1027
+ }
1028
+
1029
+ function collectWorkspaceInterfaces(workspaceRoot) {
1030
+ const interfaces = new Map()
1031
+
1032
+ walkFiles(path.resolve(workspaceRoot), (filePath) => {
1033
+ if (!filePath.endsWith('.daml')) {
1034
+ return
1035
+ }
1036
+
1037
+ const sourceModule = parseSourceModule(readFileSync(filePath, 'utf8'), '')
1038
+ if (sourceModule === undefined) {
1039
+ return
1040
+ }
1041
+
1042
+ for (const interfaceDefinition of sourceModule.interfaces.values()) {
1043
+ interfaces.set(interfaceDefinition.name, interfaceDefinition)
1044
+ }
1045
+ })
1046
+
1047
+ return interfaces
1048
+ }
1049
+
1050
+ function parseSourceModule(content, archivePath) {
1051
+ const lines = content.split(/\r?\n/)
1052
+ const topLevelLineIndexes = collectTopLevelLineIndexes(lines)
1053
+ const moduleLineIndex = lines.findIndex((line) => /^module\s+([A-Za-z0-9_.']+)\s+where\s*$/.test(line.trim()))
1054
+
1055
+ if (moduleLineIndex < 0) {
1056
+ return undefined
1057
+ }
1058
+
1059
+ const moduleName = lines[moduleLineIndex].trim().match(/^module\s+([A-Za-z0-9_.']+)\s+where\s*$/)?.[1]
1060
+ if (!moduleName) {
1061
+ return undefined
1062
+ }
1063
+
1064
+ const moduleSpan = createSpan(lines, firstNonEmptyLine(lines), lastNonEmptyLine(lines))
1065
+ const values = new Map()
1066
+ const dataTypes = new Map()
1067
+ const interfaces = new Map()
1068
+ const interfaceInstances = []
1069
+ const templates = new Map()
1070
+ const templateOrder = []
1071
+ const typeOrder = []
1072
+ const pendingValueSignatures = new Map()
1073
+
1074
+ for (let index = 0; index < topLevelLineIndexes.length; index += 1) {
1075
+ const startLineIndex = topLevelLineIndexes[index]
1076
+ const endLineIndex = findBlockEnd(lines, topLevelLineIndexes, index)
1077
+ const header = lines[startLineIndex].trim()
1078
+
1079
+ if (header.startsWith('module ')) {
1080
+ continue
1081
+ }
1082
+
1083
+ const dataMatch = /^data\s+([A-Z][A-Za-z0-9_']*)\b/.exec(header)
1084
+ if (dataMatch) {
1085
+ const fields = parseRecordFields(lines, startLineIndex, endLineIndex)
1086
+ const entity = {
1087
+ name: dataMatch[1],
1088
+ span: createSpan(lines, startLineIndex, endLineIndex),
1089
+ fields,
1090
+ }
1091
+ dataTypes.set(entity.name, entity)
1092
+ typeOrder.push(entity)
1093
+ continue
1094
+ }
1095
+
1096
+ const interfaceMatch = /^interface\s+([A-Z][A-Za-z0-9_']*)\s+where\b/.exec(header)
1097
+ if (interfaceMatch) {
1098
+ interfaces.set(
1099
+ interfaceMatch[1],
1100
+ parseInterfaceDefinition(lines, startLineIndex, endLineIndex, interfaceMatch[1]),
1101
+ )
1102
+ continue
1103
+ }
1104
+
1105
+ const templateMatch = /^template\s+([A-Z][A-Za-z0-9_']*)\b/.exec(header)
1106
+ if (templateMatch) {
1107
+ const fields = parseRecordFields(lines, startLineIndex, endLineIndex)
1108
+ const choices = parseTemplateChoices(lines, startLineIndex, endLineIndex)
1109
+ const templateInterfaceInstances = parseTemplateInterfaceInstances(
1110
+ lines,
1111
+ startLineIndex,
1112
+ endLineIndex,
1113
+ templateMatch[1],
1114
+ )
1115
+ const entity = {
1116
+ name: templateMatch[1],
1117
+ span: createSpan(lines, startLineIndex, endLineIndex),
1118
+ fields,
1119
+ choices,
1120
+ }
1121
+ templates.set(entity.name, entity)
1122
+ templateOrder.push(entity)
1123
+ typeOrder.push(entity)
1124
+ interfaceInstances.push(...templateInterfaceInstances)
1125
+ continue
1126
+ }
1127
+
1128
+ const signatureMatch = /^([a-z][A-Za-z0-9_']*)\s*:/.exec(header)
1129
+ if (signatureMatch) {
1130
+ pendingValueSignatures.set(signatureMatch[1], startLineIndex)
1131
+ continue
1132
+ }
1133
+
1134
+ const valueMatch = /^([a-z][A-Za-z0-9_']*)\b.*=/.exec(header)
1135
+ if (valueMatch && !isReservedTopLevelKeyword(valueMatch[1])) {
1136
+ const valueName = valueMatch[1]
1137
+ const valueStartLine = pendingValueSignatures.get(valueName) ?? startLineIndex
1138
+ values.set(valueName, createSpan(lines, valueStartLine, endLineIndex))
1139
+ pendingValueSignatures.delete(valueName)
1140
+ }
1141
+ }
1142
+
1143
+ const templateOrDataEntities = [...templates.values(), ...dataTypes.values()]
1144
+ const templateOrDataEntityMap = new Map(templateOrDataEntities.map((entity) => [entity.name, entity]))
1145
+
1146
+ return {
1147
+ moduleName,
1148
+ archivePath,
1149
+ moduleSpan,
1150
+ values,
1151
+ dataTypes,
1152
+ interfaces,
1153
+ interfaceInstances,
1154
+ templates,
1155
+ templateOrder,
1156
+ typeOrder,
1157
+ templateOrDataEntities,
1158
+ templateOrDataEntityMap,
1159
+ interfaceChoiceImplementations: [],
1160
+ }
1161
+ }
1162
+
1163
+ function parseInterfaceDefinition(lines, startLineIndex, endLineIndex, interfaceName) {
1164
+ const choiceMethods = new Map()
1165
+ const choiceArgumentFieldNames = new Map()
1166
+
1167
+ for (let lineIndex = startLineIndex + 1; lineIndex <= endLineIndex; lineIndex += 1) {
1168
+ const choiceMatch = /^\s*(?:nonconsuming\s+)?choice\s+([A-Z][A-Za-z0-9_']*)\s*:/.exec(lines[lineIndex])
1169
+ if (!choiceMatch) {
1170
+ continue
1171
+ }
1172
+
1173
+ const choiceName = choiceMatch[1]
1174
+ const choiceEndLineIndex = findIndentedBlockEnd(lines, lineIndex, endLineIndex)
1175
+
1176
+ for (let bodyLineIndex = lineIndex + 1; bodyLineIndex <= choiceEndLineIndex; bodyLineIndex += 1) {
1177
+ const argumentFieldMatch = /^\s+with\s+([a-z][A-Za-z0-9_']*)\s*:/.exec(lines[bodyLineIndex])
1178
+ if (argumentFieldMatch) {
1179
+ choiceArgumentFieldNames.set(choiceName, argumentFieldMatch[1])
1180
+ }
1181
+
1182
+ const methodMatch = /\bdo\s+([a-z][A-Za-z0-9_']*)\s+this\s+self\b/.exec(lines[bodyLineIndex])
1183
+ if (methodMatch) {
1184
+ choiceMethods.set(methodMatch[1], choiceName)
1185
+ break
1186
+ }
1187
+ }
1188
+ }
1189
+
1190
+ return {
1191
+ name: interfaceName,
1192
+ choiceMethods,
1193
+ choiceArgumentFieldNames,
1194
+ }
1195
+ }
1196
+
1197
+ function parseRecordFields(lines, startLineIndex, endLineIndex) {
1198
+ const fields = new Map()
1199
+ let inWithBlock = false
1200
+
1201
+ for (let lineIndex = startLineIndex + 1; lineIndex <= endLineIndex; lineIndex += 1) {
1202
+ const trimmed = lines[lineIndex].trim()
1203
+ if (trimmed === 'with') {
1204
+ inWithBlock = true
1205
+ continue
1206
+ }
1207
+ if (!inWithBlock) {
1208
+ continue
1209
+ }
1210
+ if (trimmed === 'where' || trimmed.startsWith('deriving')) {
1211
+ break
1212
+ }
1213
+ const fieldMatch = /^\s{2,}([a-z][A-Za-z0-9_']*)\s*:/.exec(lines[lineIndex])
1214
+ if (fieldMatch) {
1215
+ fields.set(fieldMatch[1], createSpan(lines, lineIndex, lineIndex))
1216
+ }
1217
+ }
1218
+
1219
+ return fields
1220
+ }
1221
+
1222
+ function parseTemplateChoices(lines, startLineIndex, endLineIndex) {
1223
+ const choices = new Map()
1224
+
1225
+ for (let lineIndex = startLineIndex + 1; lineIndex <= endLineIndex; lineIndex += 1) {
1226
+ const choiceMatch = /^\s*(?:nonconsuming\s+)?choice\s+([A-Z][A-Za-z0-9_']*)\s*:/.exec(lines[lineIndex])
1227
+ if (choiceMatch) {
1228
+ choices.set(choiceMatch[1], {
1229
+ name: choiceMatch[1],
1230
+ span: createSpan(lines, lineIndex, findIndentedBlockEnd(lines, lineIndex, endLineIndex)),
1231
+ })
1232
+ }
1233
+ }
1234
+
1235
+ return choices
1236
+ }
1237
+
1238
+ function parseTemplateInterfaceInstances(lines, startLineIndex, endLineIndex, templateName) {
1239
+ const interfaceInstances = []
1240
+
1241
+ for (let lineIndex = startLineIndex + 1; lineIndex <= endLineIndex; lineIndex += 1) {
1242
+ const instanceMatch =
1243
+ /^\s+interface instance\s+([A-Z][A-Za-z0-9_']*)\s+for\s+([A-Z][A-Za-z0-9_']*)\s+where\s*$/.exec(
1244
+ lines[lineIndex],
1245
+ )
1246
+
1247
+ if (!instanceMatch || instanceMatch[2] !== templateName) {
1248
+ continue
1249
+ }
1250
+
1251
+ const instanceEndLineIndex = findIndentedBlockEnd(lines, lineIndex, endLineIndex)
1252
+ const implementations = []
1253
+
1254
+ for (
1255
+ let methodLineIndex = lineIndex + 1;
1256
+ methodLineIndex <= instanceEndLineIndex;
1257
+ methodLineIndex += 1
1258
+ ) {
1259
+ const methodMatch = /^\s+([a-z][A-Za-z0-9_']*)\b.*=/.exec(lines[methodLineIndex])
1260
+ if (!methodMatch || isReservedTopLevelKeyword(methodMatch[1])) {
1261
+ continue
1262
+ }
1263
+
1264
+ implementations.push({
1265
+ methodName: methodMatch[1],
1266
+ span: createSpan(
1267
+ lines,
1268
+ methodLineIndex,
1269
+ findIndentedBlockEnd(lines, methodLineIndex, instanceEndLineIndex),
1270
+ ),
1271
+ })
1272
+ }
1273
+
1274
+ interfaceInstances.push({
1275
+ interfaceName: instanceMatch[1],
1276
+ templateName,
1277
+ implementations,
1278
+ })
1279
+ }
1280
+
1281
+ return interfaceInstances
1282
+ }
1283
+
1284
+ function findIndentedBlockEnd(lines, startLineIndex, endLineIndex) {
1285
+ const startIndent = indentationWidth(lines[startLineIndex])
1286
+ let lastContentLine = startLineIndex
1287
+
1288
+ for (let lineIndex = startLineIndex + 1; lineIndex <= endLineIndex; lineIndex += 1) {
1289
+ const line = lines[lineIndex]
1290
+ const trimmed = line.trim()
1291
+ if (!trimmed || trimmed.startsWith('--')) {
1292
+ continue
1293
+ }
1294
+ if (indentationWidth(line) <= startIndent) {
1295
+ break
1296
+ }
1297
+ lastContentLine = lineIndex
1298
+ }
1299
+
1300
+ return lastContentLine
1301
+ }
1302
+
1303
+ function resolveInterfaceChoiceImplementations(sourceModules, workspaceInterfaces) {
1304
+ const interfacesByName = workspaceInterfaces ?? new Map()
1305
+
1306
+ if (workspaceInterfaces === undefined) {
1307
+ for (const sourceModule of sourceModules.values()) {
1308
+ for (const interfaceDefinition of sourceModule.interfaces.values()) {
1309
+ interfacesByName.set(interfaceDefinition.name, interfaceDefinition)
1310
+ }
1311
+ }
1312
+ }
1313
+
1314
+ for (const sourceModule of sourceModules.values()) {
1315
+ sourceModule.interfaceChoiceImplementations = sourceModule.interfaceInstances.flatMap(
1316
+ (instance) => {
1317
+ const interfaceDefinition = interfacesByName.get(instance.interfaceName)
1318
+ if (interfaceDefinition === undefined) {
1319
+ return []
1320
+ }
1321
+
1322
+ return instance.implementations.flatMap((implementation) => {
1323
+ const choiceName = interfaceDefinition.choiceMethods.get(implementation.methodName)
1324
+ if (choiceName === undefined) {
1325
+ return []
1326
+ }
1327
+
1328
+ return [{
1329
+ templateName: instance.templateName,
1330
+ choiceName,
1331
+ choiceArgumentFieldName: interfaceDefinition.choiceArgumentFieldNames.get(choiceName),
1332
+ span: implementation.span,
1333
+ }]
1334
+ })
1335
+ },
1336
+ )
1337
+ }
1338
+ }
1339
+
1340
+ function collectTopLevelLineIndexes(lines) {
1341
+ const indexes = []
1342
+ for (let index = 0; index < lines.length; index += 1) {
1343
+ const line = lines[index]
1344
+ const trimmed = line.trim()
1345
+ if (!trimmed || trimmed.startsWith('--')) {
1346
+ continue
1347
+ }
1348
+ if (/^\S/.test(line)) {
1349
+ indexes.push(index)
1350
+ }
1351
+ }
1352
+ return indexes
1353
+ }
1354
+
1355
+ function findBlockEnd(lines, topLevelLineIndexes, index) {
1356
+ const nextTopLevelLineIndex = topLevelLineIndexes[index + 1] ?? lines.length
1357
+ let endLineIndex = nextTopLevelLineIndex - 1
1358
+
1359
+ while (endLineIndex > topLevelLineIndexes[index] && !lines[endLineIndex].trim()) {
1360
+ endLineIndex -= 1
1361
+ }
1362
+
1363
+ return endLineIndex
1364
+ }
1365
+
1366
+ function createSpan(lines, startLineIndex, endLineIndex) {
1367
+ return {
1368
+ startLine: startLineIndex + 1,
1369
+ startColumn: 1,
1370
+ endLine: endLineIndex + 1,
1371
+ endColumn: Math.max((lines[endLineIndex] ?? '').length + 1, 1),
1372
+ }
1373
+ }
1374
+
1375
+ function firstNonEmptyLine(lines) {
1376
+ const index = lines.findIndex((line) => line.trim().length > 0)
1377
+ return index >= 0 ? index : 0
1378
+ }
1379
+
1380
+ function lastNonEmptyLine(lines) {
1381
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
1382
+ if (lines[index].trim().length > 0) {
1383
+ return index
1384
+ }
1385
+ }
1386
+ return 0
1387
+ }
1388
+
1389
+ function isReservedTopLevelKeyword(value) {
1390
+ return new Set([
1391
+ 'choice',
1392
+ 'controller',
1393
+ 'do',
1394
+ 'ensure',
1395
+ 'let',
1396
+ 'observer',
1397
+ 'signatory',
1398
+ 'then',
1399
+ 'where',
1400
+ ]).has(value)
1401
+ }
1402
+
1403
+ function indentationWidth(line) {
1404
+ const match = /^(\s*)/.exec(line)
1405
+ return match?.[1].length ?? 0
1406
+ }
1407
+
1408
+ function statExists(candidatePath, kind) {
1409
+ try {
1410
+ const stats = statSync(path.resolve(candidatePath))
1411
+ return kind === 'file' ? stats.isFile() : stats.isDirectory()
1412
+ } catch {
1413
+ return false
1414
+ }
1415
+ }
1416
+
1417
+ function walkDirectories(rootDir, visitor) {
1418
+ visitor(rootDir)
1419
+ for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
1420
+ if (!entry.isDirectory()) {
1421
+ continue
1422
+ }
1423
+ const absolutePath = path.join(rootDir, entry.name)
1424
+ walkDirectories(absolutePath, visitor)
1425
+ }
1426
+ }
1427
+
1428
+ function walkFiles(rootDir, visitor) {
1429
+ for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
1430
+ const absolutePath = path.join(rootDir, entry.name)
1431
+ if (entry.isDirectory()) {
1432
+ walkFiles(absolutePath, visitor)
1433
+ continue
1434
+ }
1435
+ if (entry.isFile()) {
1436
+ visitor(absolutePath)
1437
+ }
1438
+ }
1439
+ }
1440
+
1441
+ function findDarFiles(rootDir) {
1442
+ const files = []
1443
+ walkFiles(rootDir, (filePath) => {
1444
+ if (filePath.endsWith('.dar')) {
1445
+ files.push(filePath)
1446
+ }
1447
+ })
1448
+ return files.sort()
1449
+ }
1450
+
1451
+ function joinArchivePath(prefix, relativePath) {
1452
+ const normalizedRelativePath = normalizeArchivePath(relativePath)
1453
+ if (!prefix) {
1454
+ return normalizedRelativePath
1455
+ }
1456
+ return `${normalizeArchivePath(prefix)}/${normalizedRelativePath}`
1457
+ }
1458
+
1459
+ function normalizeArchivePath(value) {
1460
+ return value
1461
+ .replace(/\\/g, '/')
1462
+ .replace(/^\.\/+/, '')
1463
+ .replace(/^\/+/, '')
1464
+ .replace(/\/+/g, '/')
1465
+ }
1466
+
1467
+ function defaultOutputPath(inputDar) {
1468
+ const extension = path.extname(inputDar)
1469
+ const baseName = extension ? inputDar.slice(0, -extension.length) : inputDar
1470
+ return `${baseName}-source-map.json`
1471
+ }
1472
+
1473
+ function requireValue(argv, index, flagName) {
1474
+ const value = argv[index]
1475
+ if (!value) {
1476
+ throw new Error(`${flagName} requires a value`)
1477
+ }
1478
+ return value
1479
+ }
1480
+
1481
+ function printHelp() {
1482
+ console.log(`Usage:
1483
+ node ./scripts/generate-daml-source-map.mjs \\
1484
+ --input /path/to/package.dar \\
1485
+ --workspace-root /path/to/daml/workspace \\
1486
+ [--package-dir /path/to/specific/package] \\
1487
+ [--source-prefix daml] \\
1488
+ [--debug-dalf /path/to/location-preserving.dalf] \\
1489
+ [--workspace-imports-only] \\
1490
+ [--output /path/to/source-map.json]
1491
+
1492
+ Notes:
1493
+ - The script reads the compiled main package from the DAR.
1494
+ - If --debug-dalf is provided, or the DAR already contains debug/*.dalf, the
1495
+ script emits deterministic expressionLocations from compiler-authored LF
1496
+ source spans.
1497
+ - It resolves the matching DAML package directory from daml.yaml.
1498
+ - It emits debug/source-map-compatible metadata for the main package.
1499
+ - --workspace-imports-only limits importedPackages to package ids built from
1500
+ the same workspace, so replay does not recurse into stdlib/vendor DARs.
1501
+ - Compiler-generated helper definitions fall back to the surrounding module span
1502
+ when a more specific source span cannot be inferred.`)
1503
+ }
1504
+
1505
+ await main()