@distrohelena/canton-explorer 0.1.4 → 0.1.6

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