@taqueria/plugin-ligo 0.37.21 → 0.37.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/compile.ts CHANGED
@@ -1,4 +1,13 @@
1
- import { execCmd, getArch, getArtifactsDir, sendAsyncErr, sendErr, sendJsonRes, sendWarn } from '@taqueria/node-sdk';
1
+ import {
2
+ execCmd,
3
+ getArch,
4
+ getArtifactsDir,
5
+ sendAsyncErr,
6
+ sendErr,
7
+ sendJsonRes,
8
+ sendRes,
9
+ sendWarn,
10
+ } from '@taqueria/node-sdk';
2
11
  import { access, readFile, writeFile } from 'fs/promises';
3
12
  import { basename, extname, join } from 'path';
4
13
  import {
@@ -7,34 +16,98 @@ import {
7
16
  getInputFilenameAbsPath,
8
17
  getInputFilenameRelPath,
9
18
  getLigoDockerImage,
19
+ UnionOpts,
10
20
  } from './common';
11
21
 
12
- export type TableRow = { contract: string; artifact: string };
22
+ export type TableRow = { source: string; artifact: string };
13
23
 
14
24
  export type ExprKind = 'storage' | 'default_storage' | 'parameter';
15
25
 
26
+ export type ModuleInfo = {
27
+ moduleName: string;
28
+ sourceName: string;
29
+ sourceFile: string;
30
+ syntax: 'mligo' | 'jsligo' | 'religo' | 'ligo';
31
+ type: 'file-main' | 'file-entry' | 'module-main' | 'module-entry';
32
+ };
33
+
16
34
  const COMPILE_ERR_MSG: string = 'Not compiled';
17
35
 
18
36
  const isStorageKind = (exprKind: ExprKind): boolean => exprKind === 'storage' || exprKind === 'default_storage';
19
37
 
20
- const isLIGOFile = (sourceFile: string): boolean => /.+\.(ligo|religo|mligo|jsligo)$/.test(sourceFile);
38
+ export const isSupportedLigoSyntax = (sourceFile: string) => /\.(mligo|jsligo)$/.test(sourceFile);
39
+
40
+ export const isUnsupportedLigoSyntax = (sourceFile: string) => /\.(ligo|religo)$/.test(sourceFile);
21
41
 
22
- const isStorageListFile = (sourceFile: string): boolean =>
42
+ export const isLIGOFile = (sourceFile: string) =>
43
+ isSupportedLigoSyntax(sourceFile) || isUnsupportedLigoSyntax(sourceFile);
44
+
45
+ export const isStorageListFile = (sourceFile: string): boolean =>
23
46
  /.+\.(storageList|storages)\.(ligo|religo|mligo|jsligo)$/.test(sourceFile);
24
47
 
25
- const isParameterListFile = (sourceFile: string): boolean =>
48
+ export const isParameterListFile = (sourceFile: string): boolean =>
26
49
  /.+\.(parameterList|parameters)\.(ligo|religo|mligo|jsligo)$/.test(sourceFile);
27
50
 
28
- const isContractFile = (sourceFile: string): boolean =>
29
- isLIGOFile(sourceFile) && !isStorageListFile(sourceFile) && !isParameterListFile(sourceFile);
30
-
31
- const getModuleName = async (parsedArgs: Opts, sourceFile: string): Promise<string | undefined> => {
32
- const fileContent = await readFile(getInputFilenameAbsPath(parsedArgs, sourceFile), 'utf8');
33
- if (fileContent.includes('@entry') && fileContent.includes('module')) {
34
- const match = fileContent.match(/module ([^\s]+)/);
35
- return match ? match[1] : undefined;
51
+ export const listContractModules = async (parsedArgs: UnionOpts, sourceFile: string): Promise<ModuleInfo[]> => {
52
+ try {
53
+ await getArch();
54
+ const cmd = await getListDeclarationsCmd(parsedArgs, sourceFile);
55
+ const { stderr, stdout } = await execCmd(cmd);
56
+ if (stderr.length > 0) return Promise.reject(stderr);
57
+
58
+ return JSON.parse(stdout).declarations.reduce(
59
+ (acc: ModuleInfo[], decl: string) => {
60
+ // We need to process delcarations (decl) like so:
61
+ // 1. If the decl is equal to the string "main", then the module type is "file-main" and the name of the module is the sourceFile.
62
+ // 2. If the decl is equal to $main, then the module type is "file-entry" and the name fo the module is the sourceFile.
63
+ // 3. If the decl ends with .main, then the module type is "module-main" and the name of the module is the decl without the .main suffix.
64
+ // 4. If the decl ends with .$main, then the module type is "module-entry" and the name of the module is the decl without the .$main suffix.
65
+ // Otherwise, this is not a declaration we care about.
66
+ const srcFile = removeExt(basename(sourceFile));
67
+ const syntax = extractExt(sourceFile).replace('.', '');
68
+
69
+ if (decl === 'main') {
70
+ return [...acc, { moduleName: srcFile, sourceName: sourceFile, sourceFile, type: 'file-main', syntax }];
71
+ } else if (decl === '$main') {
72
+ return [...acc, { moduleName: srcFile, sourceName: sourceFile, sourceFile, type: 'file-entry', syntax }];
73
+ } else if (decl.endsWith('.main')) {
74
+ const moduleName = decl.replace(/\.main$/, '');
75
+ return [...acc, {
76
+ moduleName,
77
+ sourceName: `${sourceFile}/${moduleName}`,
78
+ sourceFile,
79
+ type: 'module-main',
80
+ syntax,
81
+ }];
82
+ } else if (decl.endsWith('.$main')) {
83
+ const moduleName = decl.replace(/\.\$main$/, '');
84
+ return [...acc, {
85
+ moduleName,
86
+ sourceName: `${sourceFile}/${moduleName}`,
87
+ sourceFile,
88
+ type: 'module-entry',
89
+ syntax,
90
+ }];
91
+ }
92
+ return acc;
93
+ },
94
+ [],
95
+ );
96
+ } catch (err) {
97
+ emitExternalError(err, sourceFile);
98
+ return [];
36
99
  }
37
- return undefined;
100
+ };
101
+
102
+ const getListDeclarationsCmd = async (parsedArgs: UnionOpts, sourceFile: string): Promise<string> => {
103
+ const projectDir = process.env.PROJECT_DIR ?? parsedArgs.projectDir;
104
+ if (!projectDir) throw new Error(`No project directory provided`);
105
+ const baseCmd =
106
+ `DOCKER_DEFAULT_PLATFORM=linux/amd64 docker run --rm -v \"${projectDir}\":/project -w /project -u $(id -u):$(id -g) ${getLigoDockerImage()} info list-declarations`;
107
+ const inputFile = getInputFilenameRelPath(parsedArgs, sourceFile);
108
+ const flags = '--display-format json';
109
+ const cmd = `${baseCmd} ${inputFile} ${flags}`;
110
+ return cmd;
38
111
  };
39
112
 
40
113
  const extractExt = (path: string): string => {
@@ -49,27 +122,13 @@ const removeExt = (path: string): string => {
49
122
 
50
123
  const isOutputFormatJSON = (parsedArgs: Opts): boolean => parsedArgs.json;
51
124
 
52
- const getOutputContractFilename = (parsedArgs: Opts, sourceFile: string): string => {
53
- const outputFile = basename(sourceFile, extname(sourceFile));
125
+ const getOutputContractFilename = (parsedArgs: Opts, module: ModuleInfo): string => {
54
126
  const ext = isOutputFormatJSON(parsedArgs) ? '.json' : '.tz';
55
- return join(getArtifactsDir(parsedArgs), `${outputFile}${ext}`);
127
+ return join(getArtifactsDir(parsedArgs), `${module.moduleName}${ext}`);
56
128
  };
57
129
 
58
- // Get the contract name that the storage/parameter file is associated with
59
- // e.g. If sourceFile is token.storageList.mligo, then it'll return token.mligo
60
- const getContractNameForExpr = (sourceFile: string, exprKind: ExprKind): string => {
61
- try {
62
- return isStorageKind(exprKind)
63
- ? sourceFile.match(/.+(?=\.(?:storageList|storages)\.(ligo|religo|mligo|jsligo))/)!.join('.')
64
- : sourceFile.match(/.+(?=\.(?:parameterList|parameters)\.(ligo|religo|mligo|jsligo))/)!.join('.');
65
- } catch (err) {
66
- throw new Error(`Something went wrong internally when dealing with filename format: ${err}`);
67
- }
68
- };
69
-
70
- // If sourceFile is token.storageList.mligo, then it'll return token.storage.{storageName}.tz
71
- const getOutputExprFilename = (parsedArgs: Opts, sourceFile: string, exprKind: ExprKind, exprName: string): string => {
72
- const contractName = basename(getContractNameForExpr(sourceFile, exprKind), extname(sourceFile));
130
+ const getOutputExprFilename = (parsedArgs: Opts, module: ModuleInfo, exprKind: ExprKind, exprName: string): string => {
131
+ const contractName = module.moduleName;
73
132
  const ext = isOutputFormatJSON(parsedArgs) ? '.json' : '.tz';
74
133
  const outputFile = exprKind === 'default_storage'
75
134
  ? `${contractName}.default_storage${ext}`
@@ -77,78 +136,103 @@ const getOutputExprFilename = (parsedArgs: Opts, sourceFile: string, exprKind: E
77
136
  return join(getArtifactsDir(parsedArgs), `${outputFile}`);
78
137
  };
79
138
 
80
- const getCompileContractCmd = async (parsedArgs: Opts, sourceFile: string): Promise<string> => {
139
+ const getCompileContractCmd = async (parsedArgs: Opts, sourceFile: string, module: ModuleInfo): Promise<string> => {
81
140
  const projectDir = process.env.PROJECT_DIR ?? parsedArgs.projectDir;
82
- if (!projectDir) throw `No project directory provided`;
141
+ if (!projectDir) throw new Error(`No project directory provided`);
83
142
  const baseCmd =
84
143
  `DOCKER_DEFAULT_PLATFORM=linux/amd64 docker run --rm -v \"${projectDir}\":/project -w /project -u $(id -u):$(id -g) ${getLigoDockerImage()} compile contract`;
85
144
  const inputFile = getInputFilenameRelPath(parsedArgs, sourceFile);
86
- const outputFile = `-o ${getOutputContractFilename(parsedArgs, sourceFile)}`;
145
+ const outputFile = `-o ${getOutputContractFilename(parsedArgs, module)}`;
87
146
  const flags = isOutputFormatJSON(parsedArgs) ? ' --michelson-format json ' : '';
88
- const moduleName = await getModuleName(parsedArgs, sourceFile);
89
- const entryFlag = moduleName ? `-m ${moduleName}` : '';
90
- const cmd = `${baseCmd} ${inputFile} ${outputFile} ${flags}${entryFlag}`;
147
+ const moduleFlag = module.type.startsWith('file-') ? '' : `-m ${module.moduleName}`;
148
+ const cmd = `${baseCmd} ${inputFile} ${outputFile} ${flags}${moduleFlag}`;
91
149
  return cmd;
92
150
  };
93
151
 
94
- const getCompileExprCmd = (parsedArgs: Opts, sourceFile: string, exprKind: ExprKind, exprName: string): string => {
152
+ const getCompileExprCmd = (
153
+ parsedArgs: Opts,
154
+ sourceFile: string,
155
+ module: ModuleInfo,
156
+ exprKind: ExprKind,
157
+ exprName: string,
158
+ ): string => {
95
159
  const projectDir = process.env.PROJECT_DIR ?? parsedArgs.projectDir;
96
- if (!projectDir) throw `No project directory provided`;
160
+ if (!projectDir) throw new Error(`No project directory provided`);
97
161
  const compilerType = isStorageKind(exprKind) ? 'storage' : 'parameter';
98
162
  const baseCmd =
99
163
  `DOCKER_DEFAULT_PLATFORM=linux/amd64 docker run --rm -v \"${projectDir}\":/project -w /project -u $(id -u):$(id -g) ${getLigoDockerImage()} compile ${compilerType}`;
100
164
  const inputFile = getInputFilenameRelPath(parsedArgs, sourceFile);
101
- const outputFile = `-o ${getOutputExprFilename(parsedArgs, sourceFile, exprKind, exprName)}`;
165
+ const outputFile = `-o ${getOutputExprFilename(parsedArgs, module, exprKind, exprName)}`;
102
166
  const flags = isOutputFormatJSON(parsedArgs) ? ' --michelson-format json ' : '';
103
- const cmd = `${baseCmd} ${inputFile} ${exprName} ${outputFile} ${flags}`;
167
+
168
+ // Parameter and Storage list files are expected to import the smart contract file as the "Contract" module.
169
+ const moduleFlag = (() => {
170
+ switch (module.type) {
171
+ case 'file-main':
172
+ case 'file-entry':
173
+ return '-m Contract';
174
+ default:
175
+ return `-m Contract.${module.moduleName}`;
176
+ }
177
+ })();
178
+
179
+ const cmd = `${baseCmd} ${inputFile} ${exprName} ${outputFile} ${flags} ${moduleFlag}`;
104
180
  return cmd;
105
181
  };
106
182
 
107
- const compileContract = async (parsedArgs: Opts, sourceFile: string): Promise<TableRow> => {
183
+ const compileContract = async (parsedArgs: Opts, sourceFile: string, module: ModuleInfo): Promise<TableRow> => {
108
184
  try {
109
185
  await getArch();
110
- const cmd = await getCompileContractCmd(parsedArgs, sourceFile);
186
+ const cmd = await getCompileContractCmd(parsedArgs, sourceFile, module);
111
187
  const { stderr } = await execCmd(cmd);
112
188
  if (stderr.length > 0) sendWarn(stderr);
189
+
113
190
  return {
114
- contract: sourceFile,
115
- artifact: getOutputContractFilename(parsedArgs, sourceFile),
191
+ source: module.sourceName,
192
+ artifact: getOutputContractFilename(parsedArgs, module),
116
193
  };
117
194
  } catch (err) {
118
195
  emitExternalError(err, sourceFile);
119
196
  return {
120
- contract: sourceFile,
197
+ source: module.sourceName,
121
198
  artifact: COMPILE_ERR_MSG,
122
199
  };
123
200
  }
124
201
  };
125
202
 
126
- const compileExpr = (parsedArgs: Opts, sourceFile: string, exprKind: ExprKind) =>
127
- (exprName: string): Promise<TableRow> =>
128
- getArch()
129
- .then(() => getCompileExprCmd(parsedArgs, sourceFile, exprKind, exprName))
203
+ const compileExpr =
204
+ (parsedArgs: Opts, sourceFile: string, module: ModuleInfo, exprKind: ExprKind) =>
205
+ (exprName: string): Promise<TableRow> => {
206
+ return getArch()
207
+ .then(() => getCompileExprCmd(parsedArgs, sourceFile, module, exprKind, exprName))
130
208
  .then(execCmd)
131
209
  .then(({ stderr }) => {
132
210
  if (stderr.length > 0) sendWarn(stderr);
133
- const artifactName = getOutputExprFilename(parsedArgs, sourceFile, exprKind, exprName);
211
+ const artifactName = getOutputExprFilename(parsedArgs, module, exprKind, exprName);
134
212
  return {
135
- contract: sourceFile,
213
+ source: module.sourceName,
136
214
  artifact: artifactName,
137
215
  };
138
216
  })
139
217
  .catch(err => {
140
218
  emitExternalError(err, sourceFile);
141
219
  return {
142
- contract: sourceFile,
143
- artifact: COMPILE_ERR_MSG,
220
+ source: module.sourceName,
221
+ artifact: `${sourceFile} not compiled`,
144
222
  };
145
223
  });
224
+ };
146
225
 
147
226
  const getExprNames = (parsedArgs: Opts, sourceFile: string): Promise<string[]> =>
148
227
  readFile(getInputFilenameAbsPath(parsedArgs, sourceFile), 'utf8')
149
228
  .then(data => data.match(/(?<=\n\s*(let|const)\s+)[a-zA-Z0-9_]+/g) ?? []);
150
229
 
151
- const compileExprs = (parsedArgs: Opts, sourceFile: string, exprKind: ExprKind): Promise<TableRow[]> =>
230
+ const compileExprs = (
231
+ parsedArgs: Opts,
232
+ sourceFile: string,
233
+ module: ModuleInfo,
234
+ exprKind: ExprKind,
235
+ ): Promise<TableRow[]> =>
152
236
  getExprNames(parsedArgs, sourceFile)
153
237
  .then(exprNames => {
154
238
  if (exprNames.length === 0) return [];
@@ -156,165 +240,320 @@ const compileExprs = (parsedArgs: Opts, sourceFile: string, exprKind: ExprKind):
156
240
  const restExprNames = exprNames.slice(1, exprNames.length);
157
241
  const firstExprKind = isStorageKind(exprKind) ? 'default_storage' : 'parameter';
158
242
  const restExprKind = isStorageKind(exprKind) ? 'storage' : 'parameter';
159
- const firstExprResult = compileExpr(parsedArgs, sourceFile, firstExprKind)(firstExprName);
160
- const restExprResults = restExprNames.map(compileExpr(parsedArgs, sourceFile, restExprKind));
243
+ const firstExprResult = compileExpr(parsedArgs, sourceFile, module, firstExprKind)(firstExprName);
244
+ const restExprResults = restExprNames.map(compileExpr(parsedArgs, sourceFile, module, restExprKind));
161
245
  return Promise.all([firstExprResult].concat(restExprResults));
162
246
  })
163
247
  .catch(err => {
164
248
  emitExternalError(err, sourceFile);
165
249
  return [{
166
- contract: sourceFile,
250
+ source: module.sourceName,
167
251
  artifact: `No ${isStorageKind(exprKind) ? 'storage' : 'parameter'} expressions compiled`,
168
252
  }];
169
253
  })
170
254
  .then(results =>
171
255
  results.length > 0 ? results : [{
172
- contract: sourceFile,
256
+ source: module.sourceName,
173
257
  artifact: `No ${isStorageKind(exprKind) ? 'storage' : 'parameter'} expressions found`,
174
258
  }]
175
- )
176
- .then(mergeArtifactsOutput(sourceFile));
177
-
178
- // TODO: Just for backwards compatibility. Can be deleted in the future.
179
- const tryLegacyStorageNamingConvention = (parsedArgs: Opts, sourceFile: string) => {
180
- const storageListFile = `${removeExt(sourceFile)}.storages${extractExt(sourceFile)}`;
181
- const storageListFilename = getInputFilenameAbsPath(parsedArgs, storageListFile);
182
- return access(storageListFilename).then(() => {
183
- sendWarn(
184
- `Warning: The naming convention of "<CONTRACT>.storages.<EXTENSION>" is deprecated and renamed to "<CONTRACT>.storageList.<EXTENSION>". Please adjust your storage file names accordingly\n`,
185
- );
186
- return compileExprs(parsedArgs, storageListFile, 'storage');
187
- });
188
- };
189
-
190
- // TODO: Just for backwards compatibility. Can be deleted in the future.
191
- const tryLegacyParameterNamingConvention = (parsedArgs: Opts, sourceFile: string) => {
192
- const parameterListFile = `${removeExt(sourceFile)}.parameters${extractExt(sourceFile)}`;
193
- const parameterListFilename = getInputFilenameAbsPath(parsedArgs, parameterListFile);
194
- return access(parameterListFilename).then(() => {
195
- sendWarn(
196
- `Warning: The naming convention of "<CONTRACT>.parameters.<EXTENSION>" is deprecated and renamed to "<CONTRACT>.parameterList.<EXTENSION>". Please adjust your parameter file names accordingly\n`,
197
259
  );
198
- return compileExprs(parsedArgs, parameterListFile, 'parameter');
199
- });
200
- };
201
-
202
- const initContentForStorage = (sourceFile: string): string => {
203
- const linkToContract = `#include "${sourceFile}"\n\n`;
204
260
 
261
+ const initContentForStorage = (module: ModuleInfo): string => {
262
+ const linkToContract = `#import "${module.sourceFile}" "Contract"\n\n`;
205
263
  const instruction =
206
- '// Define your initial storage values as a list of LIGO variable definitions,\n// the first of which will be considered the default value to be used for origination later on\n';
207
-
208
- const ext = extractExt(sourceFile);
209
- let syntax = '';
210
- if (ext === '.ligo') syntax = '// E.g. const aStorageValue : aStorageType = 10;\n\n';
211
- else if (ext === '.religo') syntax = '// E.g. let aStorageValue : aStorageType = 10;\n\n';
212
- else if (ext === '.mligo') syntax = '// E.g. let aStorageValue : aStorageType = 10\n\n';
213
- else if (ext === '.jsligo') syntax = '// E.g. const aStorageValue : aStorageType = 10;\n\n';
214
-
215
- return linkToContract + instruction + syntax;
264
+ '// Define your initial storage values as a list of LIGO variable definitions, the first of which will be considered the default value to be used for origination later on\n';
265
+
266
+ const syntax = (() => {
267
+ const pair = [module.syntax, module.type].join('-');
268
+ switch (pair) {
269
+ case 'mligo-file-main':
270
+ return [
271
+ '// When this file was created, the smart contract was defined with a main function that was not within a named module. As such, the examples below are written with that assumption in mind.',
272
+ '',
273
+ '// If your storage is a simple value, you can define it directly',
274
+ '// E.g. let storage = 10',
275
+ '',
276
+ '// For added type-safety, you can reference the type of your storage from the contract',
277
+ '// E.g. let storage : Contract.storage = 10',
278
+ ];
279
+ break;
280
+ case 'mligo-file-entry':
281
+ return [
282
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was not within a named module. As such, the examples below are written with that assumption in mind.',
283
+ '',
284
+ '// If your storage is a simple value, you can define it directly',
285
+ '// E.g. let storage = 10',
286
+ '',
287
+ '// For added type-safety, you can reference the type of your storage from the contract',
288
+ '// E.g. let storage : Contract.storage = 10',
289
+ ];
290
+ break;
291
+ case 'mligo-module-main':
292
+ return [
293
+ '// When this file was created, the smart contract was defined with a main function that was within a named module. As such, the examples below are written with that assumption in mind.',
294
+ '',
295
+ '// If your storage is a simple value, you can define it directly',
296
+ '// E.g. let storage = 10',
297
+ '',
298
+ '// For added type-safety, you can reference the type of your storage from the contract',
299
+ `// E.g. let storage : Contract.${module.moduleName}.storage = 10`,
300
+ ];
301
+ break;
302
+ case 'mligo-module-entry':
303
+ return [
304
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was within a named module. As such, the examples below are written with that assumption in mind.',
305
+ '',
306
+ '// If your storage is a simple value, you can define it directly',
307
+ '// E.g. let storage = 10',
308
+ '',
309
+ '// For added type-safety, you can reference the type of your storage from the contract',
310
+ `// E.g. let storage : Contract.${module.moduleName}.storage = 10`,
311
+ ];
312
+ break;
313
+ case 'jsligo-file-main':
314
+ return [
315
+ '// When this file was created, the smart contract was defined with a main function that was not within a namespace. As such, the examples below are written with that assumption in mind.',
316
+ `// NOTE: The "storage" type should be exported from the contract file (${module.sourceFile})`,
317
+ '',
318
+ '// If your storage is a simple value, you can define it directly',
319
+ '// E.g. const storage = 10',
320
+ '',
321
+ '// For added type-safety, you can reference the type of your storage from the contract. This assumes that you have exported your `storage` type from the contract file.',
322
+ '// E.g. const storage : Contract.storage = 10',
323
+ ];
324
+ break;
325
+ case 'jsligo-file-entry':
326
+ return [
327
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was not within a namespace. As such, the examples below are written with that assumption in mind.',
328
+ '',
329
+ '// If your storage is a simple value, you can define it directly',
330
+ '// E.g. const storage = 10',
331
+ '',
332
+ '// For added type-safety, you can reference the type of your storage from the contract. This assumes that you have exported your `storage` type from the contract file.',
333
+ '// E.g. const storage : Contract.storage = 10',
334
+ ];
335
+ break;
336
+ case 'jsligo-module-main':
337
+ return [
338
+ '// When this file was created, the smart contract was defined with a main function that was within a namespace. As such, the examples below are written with that assumption in mind.',
339
+ `// NOTE: The "storage" type should be exported from the contract file (${module.sourceFile})`,
340
+ '',
341
+ '// If your storage is a simple value, you can define it directly',
342
+ '// E.g. const storage = 10',
343
+ '',
344
+ '// For added type-safety, you can reference the type of your storage from the contract. This assumes that you have exported your `storage` type from the contract file.',
345
+ `// E.g. const storage : Contract.${module.moduleName}.storage = 10`,
346
+ ];
347
+ break;
348
+ case 'jsligo-module-entry':
349
+ return [
350
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was within a namespace. As such, the examples below are written with that assumption in mind.',
351
+ '',
352
+ '// If your storage is a simple value, you can define it directly',
353
+ '// E.g. const storage = 10',
354
+ '',
355
+ '// For added type-safety, you can reference the type of your storage from the contract. This assumes that you have exported your `storage` type from the contract file.',
356
+ `// E.g. const storage : Contract.${module.moduleName}.storage = 10`,
357
+ ];
358
+ break;
359
+ default:
360
+ return [];
361
+ }
362
+ })();
363
+ return linkToContract + instruction + syntax.join('\n');
216
364
  };
217
365
 
218
- const initContentForParameter = (sourceFile: string): string => {
219
- const linkToContract = `#include "${sourceFile}"\n\n`;
220
-
366
+ const initContentForParameter = (module: ModuleInfo): string => {
367
+ const linkToContract = `#import "${module.sourceFile}" "Contract"\n\n`;
221
368
  const instruction = '// Define your parameter values as a list of LIGO variable definitions\n';
222
369
 
223
- const ext = extractExt(sourceFile);
224
- let syntax = '';
225
- if (ext === '.ligo') syntax = '// E.g. const aParameterValue : aParameterType = Increment(1);\n\n';
226
- else if (ext === '.religo') syntax = '// E.g. let aParameterValue : aParameterType = (Increment (1));\n\n';
227
- else if (ext === '.mligo') syntax = '// E.g. let aParameterValue : aParameterType = Increment 1\n\n';
228
- else if (ext === '.jsligo') syntax = '// E.g. const aParameterValue : aParameterType = (Increment (1));\n\n';
229
-
230
- return linkToContract + instruction + syntax;
370
+ const syntax = (() => {
371
+ const pair = [module.syntax, module.type].join('-');
372
+ switch (pair) {
373
+ case 'mligo-file-main':
374
+ return [
375
+ '// When this file was created, the smart contract was defined with a main function that was not within a named module. As such, the examples below are written with that assumption in mind.',
376
+ '',
377
+ '// If your parameter is a simple value, you can define it directly',
378
+ '// E.g. let default_parameter = 10',
379
+ '',
380
+ '// For added type-safety, you can reference the type of your parameter from the contract',
381
+ '// E.g. let default_parameter : Contract.parameter = 10',
382
+ ];
383
+ break;
384
+ case 'mligo-file-entry':
385
+ return [
386
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was not within a named module. As such, the examples below are written with that assumption in mind.',
387
+ '',
388
+ '// If your parameter is a simple value, you can define it directly',
389
+ '// E.g. let default_parameter = 10',
390
+ '',
391
+ '// For added type-safety, you can reference the type of your parameter from the contract',
392
+ '// E.g. let default_parameter : parameter_of Contract = 10',
393
+ ];
394
+ break;
395
+ case 'mligo-module-main':
396
+ return [
397
+ '// When this file was created, the smart contract was defined with a main function that was within a named module. As such, the examples below are written with that assumption in mind.',
398
+ '',
399
+ '// If your parameter is a simple value, you can define it directly',
400
+ '// E.g. let default_parameter = 10',
401
+ '',
402
+ '// For added type-safety, you can reference the type of your parameter from the contract',
403
+ `// E.g. let default_parameter : Contract.${module.moduleName}.parameter = 10`,
404
+ ];
405
+ break;
406
+ case 'mligo-module-entry':
407
+ return [
408
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was within a named module. As such, the examples below are written with that assumption in mind.',
409
+ '',
410
+ '// If your parameter is a simple value, you can define it directly',
411
+ '// E.g. let default_parameter = 10',
412
+ '',
413
+ '// For added type-safety, you can reference the type of your parameter from the contract',
414
+ `// E.g. let default_parameter : parameter_of Contract.${module.moduleName} = 10`,
415
+ ];
416
+ break;
417
+ case 'jsligo-file-main':
418
+ return [
419
+ '// When this file was created, the smart contract was defined with a main function that was not within a namespace. As such, the examples below are written with that assumption in mind.',
420
+ `// NOTE: The "parameter" type should be exported from the contract file (${module.sourceFile})`,
421
+ '',
422
+ '// If your parameter is a simple value, you can define it directly',
423
+ '// E.g. const default_parameter = 10',
424
+ '',
425
+ '// For added type-safety, you can reference the type of your parameter from the contract',
426
+ '// E.g. const default_parameter : Contract.parameter = 10',
427
+ ];
428
+ break;
429
+ case 'jsligo-file-entry':
430
+ return [
431
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was not within a namespace. As such, the examples below are written with that assumption in mind.',
432
+ '',
433
+ '// If your parameter is a simple value, you can define it directly',
434
+ '// E.g. const default_parameter = 10',
435
+ '',
436
+ '// For added type-safety, you can reference the type of your parameter from the contract',
437
+ '// E.g. const default_parameter : parameter_of Contract = 10',
438
+ ];
439
+ break;
440
+ case 'jsligo-module-main':
441
+ return [
442
+ '// When this file was created, the smart contract was defined with a main function that was within a namespace. As such, the examples below are written with that assumption in mind.',
443
+ `// NOTE: The "parameter" type should be exported from the contract file (${module.sourceFile})`,
444
+ '',
445
+ '// If your parameter is a simple value, you can define it directly',
446
+ '// E.g. const default_parameter = 10',
447
+ '',
448
+ '// For added type-safety, you can reference the type of your parameter from the contract',
449
+ `// E.g. const default_parameter : Contract.${module.moduleName}.parameter = 10`,
450
+ ];
451
+ break;
452
+ case 'jsligo-module-entry':
453
+ return [
454
+ '// When this file was created, the smart contract was defined with an entrypoint using `@entry` that was within a namespace. As such, the examples below are written with that assumption in mind.',
455
+ '',
456
+ '// If your parameter is a simple value, you can define it directly',
457
+ '// E.g. const default_parameter = 10',
458
+ '',
459
+ '// For added type-safety, you can reference the type of your parameter from the contract',
460
+ `// E.g. const default_parameter : parameter_of Contract.${module.moduleName} = 10`,
461
+ ];
462
+ break;
463
+ default:
464
+ return [];
465
+ }
466
+ })();
467
+
468
+ return linkToContract + instruction + syntax.join('\n');
231
469
  };
232
470
 
233
471
  export const compileContractWithStorageAndParameter = async (
234
472
  parsedArgs: Opts,
235
473
  sourceFile: string,
474
+ module: ModuleInfo,
236
475
  ): Promise<TableRow[]> => {
237
- const contractCompileResult = await compileContract(parsedArgs, sourceFile);
476
+ const contractCompileResult = await compileContract(parsedArgs, sourceFile, module);
238
477
  if (contractCompileResult.artifact === COMPILE_ERR_MSG) return [contractCompileResult];
239
478
 
240
- const storageListFile = `${removeExt(sourceFile)}.storageList${extractExt(sourceFile)}`;
479
+ const storageListFile = `${module.moduleName}.storageList${extractExt(sourceFile)}`;
241
480
  const storageListFilename = getInputFilenameAbsPath(parsedArgs, storageListFile);
242
- const storageCompileResult = await (access(storageListFilename)
243
- .then(() => compileExprs(parsedArgs, storageListFile, 'storage'))
244
- .catch(() => tryLegacyStorageNamingConvention(parsedArgs, sourceFile))
481
+ const storageCompileResult = await access(storageListFilename)
482
+ .then(() => compileExprs(parsedArgs, storageListFile, module, 'storage'))
245
483
  .catch(() => {
246
484
  sendWarn(
247
- `Note: storage file associated with "${sourceFile}" can't be found, so "${storageListFile}" has been created for you. Use this file to define all initial storage values for this contract\n`,
485
+ `Note: storage file associated with "${module.moduleName}" can't be found, so "${storageListFile}" has been created for you. Use this file to define all initial storage values for this contract\n`,
248
486
  );
249
- writeFile(storageListFilename, initContentForStorage(sourceFile), 'utf8');
250
- }));
487
+ return writeFile(storageListFilename, initContentForStorage(module), 'utf8');
488
+ });
251
489
 
252
- const parameterListFile = `${removeExt(sourceFile)}.parameterList${extractExt(sourceFile)}`;
490
+ const parameterListFile = `${module.moduleName}.parameterList${extractExt(sourceFile)}`;
253
491
  const parameterListFilename = getInputFilenameAbsPath(parsedArgs, parameterListFile);
254
- const parameterCompileResult = await (access(parameterListFilename)
255
- .then(() => compileExprs(parsedArgs, parameterListFile, 'parameter'))
256
- .catch(() => tryLegacyParameterNamingConvention(parsedArgs, sourceFile))
492
+ const parameterCompileResult = await access(parameterListFilename)
493
+ .then(() => compileExprs(parsedArgs, parameterListFile, module, 'parameter'))
257
494
  .catch(() => {
258
495
  sendWarn(
259
- `Note: parameter file associated with "${sourceFile}" can't be found, so "${parameterListFile}" has been created for you. Use this file to define all parameter values for this contract\n`,
496
+ `Note: parameter file associated with "${module.moduleName}" can't be found, so "${parameterListFile}" has been created for you. Use this file to define all parameter values for this contract\n`,
260
497
  );
261
- writeFile(parameterListFilename, initContentForParameter(sourceFile), 'utf8');
262
- }));
498
+ return writeFile(parameterListFilename, initContentForParameter(module), 'utf8');
499
+ });
263
500
 
264
- let compileResults: TableRow[] = [contractCompileResult];
265
- if (storageCompileResult) compileResults = compileResults.concat(storageCompileResult);
266
- if (parameterCompileResult) compileResults = compileResults.concat(parameterCompileResult);
267
- return compileResults;
268
- };
501
+ const storageArtifacts = storageCompileResult ? storageCompileResult.map(res => res.artifact).join('\n') : '';
502
+ const parameterArtifacts = parameterCompileResult ? parameterCompileResult.map(res => res.artifact).join('\n') : '';
269
503
 
270
- /*
271
- Compiling storage/parameter file amounts to compiling multiple expressions in that file,
272
- resulting in multiple rows with the same file name but different artifact names.
273
- This will merge these rows into one row with just one mention of the file name.
274
- e.g.
275
- ┌─────────────────────────┬─────────────────────────────────────────────┐
276
- Contract │ Artifact │
277
- ├─────────────────────────┼─────────────────────────────────────────────┤
278
- hello.storageList.mligo │ artifacts/hello.default_storage.storage1.tz │
279
- ├─────────────────────────┼─────────────────────────────────────────────┤
280
- │ hello.storageList.mligo │ artifacts/hello.storage.storage2.tz │
281
- └─────────────────────────┴─────────────────────────────────────────────┘
282
- versus
283
- ┌─────────────────────────┬─────────────────────────────────────────────┐
284
- │ Contract │ Artifact │
285
- ├─────────────────────────┼─────────────────────────────────────────────┤
286
- │ hello.storageList.mligo │ artifacts/hello.default_storage.storage1.tz │
287
- │ │ artifacts/hello.storage.storage2.tz │
288
- └─────────────────────────┴─────────────────────────────────────────────┘
289
- */
290
- const mergeArtifactsOutput = (sourceFile: string) =>
291
- (tableRows: TableRow[]): TableRow[] => {
292
- const artifactsOutput = tableRows.reduce(
293
- (acc: string, row: TableRow) => row.artifact === COMPILE_ERR_MSG ? acc : `${acc}${row.artifact}\n`,
294
- '',
295
- );
296
- return [{
297
- contract: sourceFile,
298
- artifact: artifactsOutput,
299
- }];
504
+ const combinedArtifact = [
505
+ contractCompileResult.artifact,
506
+ storageArtifacts,
507
+ parameterArtifacts,
508
+ ].filter(Boolean).join('\n');
509
+
510
+ const combinedRow: TableRow = {
511
+ source: module.sourceName,
512
+ artifact: combinedArtifact,
300
513
  };
301
514
 
302
- const compile = (parsedArgs: Opts): Promise<void> => {
303
- const sourceFile = parsedArgs.sourceFile!;
304
- let p: Promise<TableRow[]>;
305
- if (isStorageListFile(sourceFile)) p = compileExprs(parsedArgs, sourceFile, 'storage');
306
- else if (isParameterListFile(sourceFile)) p = compileExprs(parsedArgs, sourceFile, 'parameter');
307
- else if (isContractFile(sourceFile)) p = compileContractWithStorageAndParameter(parsedArgs, sourceFile);
308
- else {
309
- return sendAsyncErr(
310
- `${sourceFile} doesn't have a valid LIGO extension ('.ligo', '.religo', '.mligo' or '.jsligo')`,
311
- );
515
+ return [combinedRow];
516
+ };
517
+
518
+ export const compile = async (parsedArgs: Opts): Promise<void> => {
519
+ const sourceFile = parsedArgs.sourceFile;
520
+ if (!isLIGOFile(sourceFile)) {
521
+ sendErr(`${sourceFile} is not a LIGO file`);
522
+ return;
523
+ }
524
+ if (isStorageListFile(sourceFile) || isParameterListFile(sourceFile)) {
525
+ sendErr(`Storage and parameter list files are not meant to be compiled directly`);
526
+ return;
527
+ }
528
+ if (isUnsupportedLigoSyntax(sourceFile)) {
529
+ sendErr(`Unsupported LIGO syntax detected in ${sourceFile}. Note, we only support .jsligo and .mligo files.`);
530
+ return;
531
+ }
532
+
533
+ try {
534
+ const modules = await listContractModules(parsedArgs, sourceFile);
535
+ if (modules.length === 0) {
536
+ return sendJsonRes([
537
+ {
538
+ source: sourceFile,
539
+ artifact: `No contract modules found in "${sourceFile}"`,
540
+ },
541
+ ]);
542
+ }
543
+
544
+ let allCompileResults: TableRow[] = [];
545
+ for (const module of modules) {
546
+ // If we're only to compile a particular module, then we'll skip any that don't match
547
+ if (parsedArgs.module && parsedArgs.module !== module.moduleName) continue;
548
+
549
+ const compileResults = await compileContractWithStorageAndParameter(parsedArgs, sourceFile, module);
550
+ allCompileResults = allCompileResults.concat(compileResults);
551
+ }
552
+
553
+ sendJsonRes(allCompileResults, { footer: `\nCompiled ${allCompileResults.length} contract(s) in "${sourceFile}"` });
554
+ } catch (err) {
555
+ sendErr(`Error processing "${sourceFile}": ${err}`);
312
556
  }
313
- return p.then(sendJsonRes).catch(err => sendErr(err, false));
314
557
  };
315
558
 
316
559
  export default compile;
317
- export const ___TEST___ = {
318
- getContractNameForExpr,
319
- getOutputExprFilename,
320
- };