ic-mops 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/RELEASE.md +198 -0
  3. package/bundle/cli.tgz +0 -0
  4. package/check-requirements.ts +3 -8
  5. package/cli.ts +94 -11
  6. package/commands/bench/bench-canister.mo +17 -6
  7. package/commands/bench.ts +13 -16
  8. package/commands/build.ts +5 -6
  9. package/commands/check.ts +121 -0
  10. package/commands/format.ts +3 -18
  11. package/commands/lint.ts +92 -0
  12. package/commands/sync.ts +2 -8
  13. package/commands/test/test.ts +10 -20
  14. package/commands/toolchain/index.ts +21 -8
  15. package/commands/toolchain/lintoko.ts +54 -0
  16. package/commands/toolchain/toolchain-utils.ts +2 -0
  17. package/commands/watch/error-checker.ts +8 -2
  18. package/commands/watch/warning-checker.ts +8 -2
  19. package/constants.ts +23 -0
  20. package/dist/check-requirements.js +3 -8
  21. package/dist/cli.js +73 -11
  22. package/dist/commands/bench/bench-canister.mo +17 -6
  23. package/dist/commands/bench.js +7 -15
  24. package/dist/commands/build.js +5 -6
  25. package/dist/commands/check.d.ts +6 -0
  26. package/dist/commands/check.js +82 -0
  27. package/dist/commands/format.js +3 -16
  28. package/dist/commands/lint.d.ts +7 -0
  29. package/dist/commands/lint.js +69 -0
  30. package/dist/commands/sync.js +2 -7
  31. package/dist/commands/test/test.js +10 -18
  32. package/dist/commands/toolchain/index.d.ts +2 -2
  33. package/dist/commands/toolchain/index.js +18 -7
  34. package/dist/commands/toolchain/lintoko.d.ts +8 -0
  35. package/dist/commands/toolchain/lintoko.js +36 -0
  36. package/dist/commands/toolchain/toolchain-utils.d.ts +1 -0
  37. package/dist/commands/toolchain/toolchain-utils.js +1 -0
  38. package/dist/commands/watch/error-checker.js +8 -2
  39. package/dist/commands/watch/warning-checker.js +8 -2
  40. package/dist/constants.d.ts +15 -0
  41. package/dist/constants.js +21 -0
  42. package/dist/environments/nodejs/cli.js +6 -1
  43. package/dist/error.d.ts +1 -1
  44. package/dist/helpers/autofix-motoko.d.ts +26 -0
  45. package/dist/helpers/autofix-motoko.js +150 -0
  46. package/dist/helpers/get-moc-version.d.ts +2 -0
  47. package/dist/helpers/get-moc-version.js +10 -1
  48. package/dist/mops.d.ts +1 -0
  49. package/dist/mops.js +12 -1
  50. package/dist/package.json +3 -2
  51. package/dist/tests/build-no-dfx.test.d.ts +1 -0
  52. package/dist/tests/build-no-dfx.test.js +9 -0
  53. package/dist/tests/build.test.d.ts +1 -0
  54. package/dist/tests/build.test.js +18 -0
  55. package/dist/tests/check-candid.test.d.ts +1 -0
  56. package/dist/tests/check-candid.test.js +20 -0
  57. package/dist/tests/check-fix.test.d.ts +1 -0
  58. package/dist/tests/check-fix.test.js +89 -0
  59. package/dist/tests/check.test.d.ts +1 -0
  60. package/dist/tests/check.test.js +37 -0
  61. package/dist/tests/cli.test.js +4 -57
  62. package/dist/tests/helpers.d.ts +22 -0
  63. package/dist/tests/helpers.js +43 -0
  64. package/dist/tests/lint.test.d.ts +1 -0
  65. package/dist/tests/lint.test.js +15 -0
  66. package/dist/tests/moc-args.test.d.ts +1 -0
  67. package/dist/tests/moc-args.test.js +17 -0
  68. package/dist/tests/toolchain.test.d.ts +1 -0
  69. package/dist/tests/toolchain.test.js +11 -0
  70. package/dist/types.d.ts +8 -1
  71. package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
  72. package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
  73. package/environments/nodejs/cli.ts +7 -1
  74. package/error.ts +1 -1
  75. package/helpers/autofix-motoko.ts +240 -0
  76. package/helpers/get-moc-version.ts +12 -1
  77. package/mops.ts +15 -1
  78. package/package.json +3 -2
  79. package/tests/__snapshots__/build-no-dfx.test.ts.snap +11 -0
  80. package/tests/__snapshots__/build.test.ts.snap +77 -0
  81. package/tests/__snapshots__/check-candid.test.ts.snap +73 -0
  82. package/tests/__snapshots__/check-fix.test.ts.snap +261 -0
  83. package/tests/__snapshots__/check.test.ts.snap +81 -0
  84. package/tests/__snapshots__/lint.test.ts.snap +78 -0
  85. package/tests/build/no-dfx/mops.toml +5 -0
  86. package/tests/build/no-dfx/src/Main.mo +5 -0
  87. package/tests/build-no-dfx.test.ts +10 -0
  88. package/tests/build.test.ts +24 -0
  89. package/tests/check/error/Error.mo +7 -0
  90. package/tests/check/error/mops.toml +2 -0
  91. package/tests/check/fix/M0223.mo +11 -0
  92. package/tests/check/fix/M0236.mo +11 -0
  93. package/tests/check/fix/M0237.mo +11 -0
  94. package/tests/check/fix/Ok.mo +7 -0
  95. package/tests/check/fix/edit-suggestions.mo +143 -0
  96. package/tests/check/fix/mops.toml +5 -0
  97. package/tests/check/fix/overlapping.mo +10 -0
  98. package/tests/check/fix/transitive-lib.mo +9 -0
  99. package/tests/check/fix/transitive-main.mo +9 -0
  100. package/tests/check/moc-args/Warning.mo +5 -0
  101. package/tests/check/moc-args/mops.toml +2 -0
  102. package/tests/check/success/Ok.mo +5 -0
  103. package/tests/check/success/Warning.mo +5 -0
  104. package/tests/check/success/mops.toml +2 -0
  105. package/tests/check-candid.test.ts +22 -0
  106. package/tests/check-fix.test.ts +134 -0
  107. package/tests/check.test.ts +51 -0
  108. package/tests/cli.test.ts +4 -74
  109. package/tests/helpers.ts +58 -0
  110. package/tests/lint/lints/no-bool-switch.toml +9 -0
  111. package/tests/lint/mops.toml +4 -0
  112. package/tests/lint/src/NoBoolSwitch.mo +8 -0
  113. package/tests/lint/src/Ok.mo +5 -0
  114. package/tests/lint.test.ts +17 -0
  115. package/tests/moc-args.test.ts +19 -0
  116. package/tests/toolchain/mock +2 -0
  117. package/tests/toolchain/mops.toml +2 -0
  118. package/tests/toolchain.test.ts +12 -0
  119. package/types.ts +8 -1
  120. package/wasm/Cargo.lock +101 -54
  121. package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
  122. package/wasm/pkg/web/wasm_bg.wasm +0 -0
  123. package/.DS_Store +0 -0
  124. package/bundle/bench/bench-canister.mo +0 -121
  125. package/bundle/bench/user-bench.mo +0 -10
  126. package/bundle/bin/moc-wrapper.sh +0 -40
  127. package/bundle/bin/mops.js +0 -3
  128. package/bundle/cli.js +0 -2144
  129. package/bundle/declarations/bench/bench.did +0 -30
  130. package/bundle/declarations/bench/bench.did.d.ts +0 -33
  131. package/bundle/declarations/bench/bench.did.js +0 -30
  132. package/bundle/declarations/bench/index.d.ts +0 -50
  133. package/bundle/declarations/bench/index.js +0 -40
  134. package/bundle/declarations/main/index.d.ts +0 -50
  135. package/bundle/declarations/main/index.js +0 -40
  136. package/bundle/declarations/main/main.did +0 -428
  137. package/bundle/declarations/main/main.did.d.ts +0 -348
  138. package/bundle/declarations/main/main.did.js +0 -406
  139. package/bundle/declarations/storage/index.d.ts +0 -50
  140. package/bundle/declarations/storage/index.js +0 -30
  141. package/bundle/declarations/storage/storage.did +0 -46
  142. package/bundle/declarations/storage/storage.did.d.ts +0 -40
  143. package/bundle/declarations/storage/storage.did.js +0 -38
  144. package/bundle/package.json +0 -36
  145. package/bundle/templates/README.md +0 -13
  146. package/bundle/templates/licenses/Apache-2.0 +0 -202
  147. package/bundle/templates/licenses/Apache-2.0-NOTICE +0 -13
  148. package/bundle/templates/licenses/MIT +0 -21
  149. package/bundle/templates/mops-publish.yml +0 -17
  150. package/bundle/templates/mops-test.yml +0 -24
  151. package/bundle/templates/src/lib.mo +0 -15
  152. package/bundle/templates/test/lib.test.mo +0 -4
  153. package/bundle/wasm_bg.wasm +0 -0
  154. package/bundle/xhr-sync-worker.js +0 -59
  155. package/dist/wasm/pkg/bundler/package.json +0 -20
  156. package/dist/wasm/pkg/bundler/wasm.d.ts +0 -3
  157. package/dist/wasm/pkg/bundler/wasm.js +0 -5
  158. package/dist/wasm/pkg/bundler/wasm_bg.js +0 -93
  159. package/dist/wasm/pkg/bundler/wasm_bg.wasm +0 -0
  160. package/dist/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
  161. package/tests/__snapshots__/cli.test.ts.snap +0 -202
  162. package/tests/build/success/.dfx/local/canister_ids.json +0 -17
  163. package/tests/build/success/.dfx/local/canisters/bar/bar.did +0 -3
  164. package/tests/build/success/.dfx/local/canisters/bar/bar.most +0 -4
  165. package/tests/build/success/.dfx/local/canisters/bar/bar.wasm +0 -0
  166. package/tests/build/success/.dfx/local/canisters/bar/constructor.did +0 -3
  167. package/tests/build/success/.dfx/local/canisters/bar/index.js +0 -42
  168. package/tests/build/success/.dfx/local/canisters/bar/init_args.txt +0 -1
  169. package/tests/build/success/.dfx/local/canisters/bar/service.did +0 -3
  170. package/tests/build/success/.dfx/local/canisters/bar/service.did.d.ts +0 -7
  171. package/tests/build/success/.dfx/local/canisters/bar/service.did.js +0 -4
  172. package/tests/build/success/.dfx/local/canisters/foo/constructor.did +0 -3
  173. package/tests/build/success/.dfx/local/canisters/foo/foo.did +0 -3
  174. package/tests/build/success/.dfx/local/canisters/foo/foo.most +0 -4
  175. package/tests/build/success/.dfx/local/canisters/foo/foo.wasm +0 -0
  176. package/tests/build/success/.dfx/local/canisters/foo/index.js +0 -42
  177. package/tests/build/success/.dfx/local/canisters/foo/init_args.txt +0 -1
  178. package/tests/build/success/.dfx/local/canisters/foo/service.did +0 -3
  179. package/tests/build/success/.dfx/local/canisters/foo/service.did.d.ts +0 -7
  180. package/tests/build/success/.dfx/local/canisters/foo/service.did.js +0 -4
  181. package/tests/build/success/.dfx/local/lsp/ucwa4-rx777-77774-qaada-cai.did +0 -3
  182. package/tests/build/success/.dfx/local/lsp/ulvla-h7777-77774-qaacq-cai.did +0 -3
  183. package/tests/build/success/.dfx/local/network-id +0 -4
  184. package/wasm/pkg/bundler/package.json +0 -20
  185. package/wasm/pkg/bundler/wasm.d.ts +0 -3
  186. package/wasm/pkg/bundler/wasm.js +0 -5
  187. package/wasm/pkg/bundler/wasm_bg.js +0 -93
  188. package/wasm/pkg/bundler/wasm_bg.wasm +0 -0
  189. package/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
@@ -11,6 +11,8 @@ import { checkRequirements } from "../../check-requirements.js";
11
11
  import * as moc from "./moc.js";
12
12
  import * as pocketIc from "./pocket-ic.js";
13
13
  import * as wasmtime from "./wasmtime.js";
14
+ import * as lintoko from "./lintoko.js";
15
+ import { FILE_PATH_REGEX } from "../../constants.js";
14
16
  function getToolUtils(tool) {
15
17
  if (tool === "moc") {
16
18
  return moc;
@@ -21,12 +23,15 @@ function getToolUtils(tool) {
21
23
  else if (tool === "wasmtime") {
22
24
  return wasmtime;
23
25
  }
26
+ else if (tool === "lintoko") {
27
+ return lintoko;
28
+ }
24
29
  else {
25
30
  console.error(`Unknown tool '${tool}'`);
26
31
  process.exit(1);
27
32
  }
28
33
  }
29
- async function ensureToolchainInited({ strict = true } = {}) {
34
+ async function checkToolchainInited({ strict = false } = {}) {
30
35
  // auto init in CI
31
36
  if (process.env.CI) {
32
37
  await init({ silent: true });
@@ -47,8 +52,8 @@ async function ensureToolchainInited({ strict = true } = {}) {
47
52
  }
48
53
  }
49
54
  catch { }
50
- console.error('Toolchain management is not initialized. Run "mops toolchain init"');
51
- process.exit(1);
55
+ process.stderr.write(`${chalk.yellow('Toolchain management is not initialized. Run "mops toolchain init" to use with dfx.')}\n`);
56
+ return false;
52
57
  }
53
58
  // update shell config files to set DFX_MOC_PATH to moc-wrapper
54
59
  async function init({ reset = false, silent = false } = {}) {
@@ -172,6 +177,9 @@ async function installAll({ silent = false, verbose = false } = {}) {
172
177
  verbose,
173
178
  });
174
179
  }
180
+ if (config.toolchain?.lintoko) {
181
+ await download("lintoko", config.toolchain.lintoko, { silent, verbose });
182
+ }
175
183
  if (!silent) {
176
184
  logUpdate.clear();
177
185
  console.log(chalk.green("Toolchain installed"));
@@ -204,7 +212,7 @@ async function promptVersion(tool) {
204
212
  // download binary and set version in mops.toml
205
213
  async function use(tool, version) {
206
214
  if (tool === "moc") {
207
- await ensureToolchainInited();
215
+ await checkToolchainInited();
208
216
  }
209
217
  if (!version) {
210
218
  version = await promptVersion(tool);
@@ -232,7 +240,7 @@ async function use(tool, version) {
232
240
  // download latest binary and set version in mops.toml
233
241
  async function update(tool) {
234
242
  if (tool === "moc") {
235
- await ensureToolchainInited();
243
+ await checkToolchainInited();
236
244
  }
237
245
  let config = readConfig();
238
246
  config.toolchain = config.toolchain || {};
@@ -273,8 +281,11 @@ async function bin(tool, { fallback = false } = {}) {
273
281
  let config = readConfig();
274
282
  let version = config.toolchain?.[tool];
275
283
  if (version) {
284
+ if (version.match(FILE_PATH_REGEX)) {
285
+ return version;
286
+ }
276
287
  if (tool === "moc") {
277
- await ensureToolchainInited();
288
+ await checkToolchainInited();
278
289
  }
279
290
  await download(tool, version, { silent: true });
280
291
  if (tool === "moc") {
@@ -300,5 +311,5 @@ export let toolchain = {
300
311
  update,
301
312
  bin,
302
313
  installAll,
303
- ensureToolchainInited,
314
+ checkToolchainInited,
304
315
  };
@@ -0,0 +1,8 @@
1
+ export declare let repo: string;
2
+ export declare let getLatestReleaseTag: () => Promise<string>;
3
+ export declare let getReleases: () => Promise<any>;
4
+ export declare let isCached: (version: string) => boolean;
5
+ export declare let download: (version: string, { silent, verbose }?: {
6
+ silent?: boolean | undefined;
7
+ verbose?: boolean | undefined;
8
+ }) => Promise<void>;
@@ -0,0 +1,36 @@
1
+ import process from "node:process";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { globalCacheDir } from "../../mops.js";
5
+ import * as toolchainUtils from "./toolchain-utils.js";
6
+ let cacheDir = path.join(globalCacheDir, "lintoko");
7
+ export let repo = "caffeinelabs/lintoko";
8
+ export let getLatestReleaseTag = async () => {
9
+ return toolchainUtils.getLatestReleaseTag(repo);
10
+ };
11
+ export let getReleases = async () => {
12
+ return toolchainUtils.getReleases(repo);
13
+ };
14
+ export let isCached = (version) => {
15
+ let dir = path.join(cacheDir, version);
16
+ return fs.existsSync(dir) && fs.existsSync(path.join(dir, "lintoko"));
17
+ };
18
+ export let download = async (version, { silent = false, verbose = false } = {}) => {
19
+ if (!version) {
20
+ console.error("version is not defined");
21
+ process.exit(1);
22
+ }
23
+ if (isCached(version)) {
24
+ if (verbose) {
25
+ console.log(`lintoko ${version} is already installed`);
26
+ }
27
+ return;
28
+ }
29
+ let platform = process.platform == "darwin" ? "apple-darwin" : "unknown-linux-gnu";
30
+ let arch = process.arch.startsWith("arm") ? "aarch64" : "x86_64";
31
+ let url = `https://github.com/caffeinelabs/lintoko/releases/download/v${version}/lintoko-${arch}-${platform}.tar.xz`;
32
+ if (verbose && !silent) {
33
+ console.log(`Downloading ${url}`);
34
+ }
35
+ await toolchainUtils.downloadAndExtract(url, path.join(cacheDir, version), "lintoko");
36
+ };
@@ -1,4 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
+ export declare const TOOLCHAINS: string[];
2
3
  export declare let tryDownloadFile: (url: string) => Promise<Buffer | null>;
3
4
  export declare let downloadAndExtract: (url: string, destDir: string, destFileName?: string) => Promise<void>;
4
5
  export declare let getLatestReleaseTag: (repo: string) => Promise<string>;
@@ -10,6 +10,7 @@ import { deleteSync } from "del";
10
10
  import { Octokit } from "octokit";
11
11
  import { extract as extractTar } from "tar";
12
12
  import { getRootDir } from "../../mops.js";
13
+ export const TOOLCHAINS = ["moc", "wasmtime", "pocket-ic", "lintoko"];
13
14
  export let tryDownloadFile = async (url) => {
14
15
  let res = await fetch(url);
15
16
  if (!res.ok) {
@@ -3,7 +3,7 @@ import { promisify } from "node:util";
3
3
  import os from "node:os";
4
4
  import chalk from "chalk";
5
5
  import { getMocPath } from "../../helpers/get-moc-path.js";
6
- import { getRootDir } from "../../mops.js";
6
+ import { getGlobalMocArgs, getRootDir, readConfig } from "../../mops.js";
7
7
  import { sources } from "../sources.js";
8
8
  import { parallel } from "../../parallel.js";
9
9
  import { globMoFiles } from "./globMoFiles.js";
@@ -31,12 +31,18 @@ export class ErrorChecker {
31
31
  let rootDir = getRootDir();
32
32
  let mocPath = getMocPath();
33
33
  let deps = await sources({ cwd: rootDir });
34
+ let globalMocArgs = getGlobalMocArgs(readConfig());
34
35
  let paths = globMoFiles(rootDir);
35
36
  this.totalFiles = paths.length;
36
37
  this.processedFiles = 0;
37
38
  await parallel(os.cpus().length, paths, async (file) => {
38
39
  try {
39
- await promisify(execFile)(mocPath, ["--check", ...deps.flatMap((x) => x.split(" ")), file], { cwd: rootDir });
40
+ await promisify(execFile)(mocPath, [
41
+ "--check",
42
+ ...deps.flatMap((x) => x.split(" ")),
43
+ ...globalMocArgs,
44
+ file,
45
+ ], { cwd: rootDir });
40
46
  }
41
47
  catch (error) {
42
48
  error.message.split("\n").forEach((line) => {
@@ -3,7 +3,7 @@ import { promisify } from "node:util";
3
3
  import os from "node:os";
4
4
  import chalk from "chalk";
5
5
  import { getMocPath } from "../../helpers/get-moc-path.js";
6
- import { getRootDir } from "../../mops.js";
6
+ import { getGlobalMocArgs, getRootDir, readConfig } from "../../mops.js";
7
7
  import { sources } from "../sources.js";
8
8
  import { parallel } from "../../parallel.js";
9
9
  import { globMoFiles } from "./globMoFiles.js";
@@ -51,6 +51,7 @@ export class WarningChecker {
51
51
  let rootDir = getRootDir();
52
52
  let mocPath = getMocPath();
53
53
  let deps = await sources({ cwd: rootDir });
54
+ let globalMocArgs = getGlobalMocArgs(readConfig());
54
55
  let paths = globMoFiles(rootDir);
55
56
  this.totalFiles = paths.length;
56
57
  this.processedFiles = 0;
@@ -58,7 +59,12 @@ export class WarningChecker {
58
59
  let controller = new AbortController();
59
60
  let { signal } = controller;
60
61
  this.controllers.set(file, controller);
61
- let { stderr } = await promisify(execFile)(mocPath, ["--check", ...deps.flatMap((x) => x.split(" ")), file], { cwd: rootDir, signal }).catch((error) => {
62
+ let { stderr } = await promisify(execFile)(mocPath, [
63
+ "--check",
64
+ ...deps.flatMap((x) => x.split(" ")),
65
+ ...globalMocArgs,
66
+ file,
67
+ ], { cwd: rootDir, signal }).catch((error) => {
62
68
  if (error.code === "ABORT_ERR") {
63
69
  return { stderr: "" };
64
70
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Common ignore patterns for glob operations across Motoko files.
3
+ */
4
+ export declare const MOTOKO_IGNORE_PATTERNS: string[];
5
+ /**
6
+ * Common glob configuration for Motoko file operations
7
+ */
8
+ export declare const MOTOKO_GLOB_CONFIG: {
9
+ nocase: boolean;
10
+ ignore: string[];
11
+ };
12
+ /**
13
+ * Regex to match a file path for dependency and toolchain versions
14
+ */
15
+ export declare const FILE_PATH_REGEX: RegExp;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Common ignore patterns for glob operations across Motoko files.
3
+ */
4
+ export const MOTOKO_IGNORE_PATTERNS = [
5
+ "**/node_modules/**",
6
+ "**/.mops/**",
7
+ "**/.vessel/**",
8
+ "**/.git/**",
9
+ "**/dist/**",
10
+ ];
11
+ /**
12
+ * Common glob configuration for Motoko file operations
13
+ */
14
+ export const MOTOKO_GLOB_CONFIG = {
15
+ nocase: true,
16
+ ignore: MOTOKO_IGNORE_PATTERNS,
17
+ };
18
+ /**
19
+ * Regex to match a file path for dependency and toolchain versions
20
+ */
21
+ export const FILE_PATH_REGEX = /^(\.?\.)?\//;
@@ -1,4 +1,9 @@
1
- import * as wasm from "../../wasm/pkg/nodejs/wasm.js";
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
2
4
  import { setWasmBindings } from "../../wasm.js";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const require = createRequire(import.meta.url);
7
+ const wasm = require(path.join(__dirname, "../../wasm/pkg/nodejs/wasm.js"));
3
8
  setWasmBindings(wasm);
4
9
  export * from "../../cli.js";
package/dist/error.d.ts CHANGED
@@ -1 +1 @@
1
- export declare function cliError(...args: unknown[]): void;
1
+ export declare function cliError(...args: unknown[]): never;
@@ -0,0 +1,26 @@
1
+ interface MocSpan {
2
+ file: string;
3
+ line_start: number;
4
+ column_start: number;
5
+ line_end: number;
6
+ column_end: number;
7
+ is_primary: boolean;
8
+ label: string | null;
9
+ suggested_replacement: string | null;
10
+ suggestion_applicability: string | null;
11
+ }
12
+ export interface MocDiagnostic {
13
+ message: string;
14
+ code: string;
15
+ level: string;
16
+ spans: MocSpan[];
17
+ notes: string[];
18
+ }
19
+ export declare function parseDiagnostics(stdout: string): MocDiagnostic[];
20
+ export interface AutofixResult {
21
+ /** Map of file path → diagnostic codes fixed in that file */
22
+ fixedFiles: Map<string, string[]>;
23
+ totalFixCount: number;
24
+ }
25
+ export declare function autofixMotoko(mocPath: string, files: string[], mocArgs: string[]): Promise<AutofixResult | null>;
26
+ export {};
@@ -0,0 +1,150 @@
1
+ import { readFile, writeFile } from "node:fs/promises";
2
+ import { resolve } from "node:path";
3
+ import { execa } from "execa";
4
+ import { TextDocument, } from "vscode-languageserver-textdocument";
5
+ export function parseDiagnostics(stdout) {
6
+ return stdout
7
+ .split("\n")
8
+ .filter((l) => l.trim())
9
+ .map((l) => {
10
+ try {
11
+ return JSON.parse(l);
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ })
17
+ .filter((d) => d !== null);
18
+ }
19
+ function extractDiagnosticFixes(diagnostics) {
20
+ const result = new Map();
21
+ for (const diag of diagnostics) {
22
+ const editsByFile = new Map();
23
+ for (const span of diag.spans) {
24
+ if (span.suggestion_applicability === "MachineApplicable" &&
25
+ span.suggested_replacement !== null) {
26
+ const file = resolve(span.file);
27
+ const edits = editsByFile.get(file) ?? [];
28
+ edits.push({
29
+ range: {
30
+ start: {
31
+ line: span.line_start - 1,
32
+ character: span.column_start - 1,
33
+ },
34
+ end: {
35
+ line: span.line_end - 1,
36
+ character: span.column_end - 1,
37
+ },
38
+ },
39
+ newText: span.suggested_replacement,
40
+ });
41
+ editsByFile.set(file, edits);
42
+ }
43
+ }
44
+ for (const [file, edits] of editsByFile) {
45
+ const existing = result.get(file) ?? [];
46
+ existing.push({ code: diag.code, edits });
47
+ result.set(file, existing);
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ function normalizeRange(range) {
53
+ const { start, end } = range;
54
+ if (start.line > end.line ||
55
+ (start.line === end.line && start.character > end.character)) {
56
+ return { start: end, end: start };
57
+ }
58
+ return range;
59
+ }
60
+ /**
61
+ * Applies diagnostic fixes to a document, processing each diagnostic as
62
+ * an atomic unit. If any edit from a diagnostic overlaps with an already-accepted
63
+ * edit, the entire diagnostic is skipped (picked up in subsequent iterations).
64
+ * Based on vscode-languageserver-textdocument's TextDocument.applyEdits.
65
+ */
66
+ function applyDiagnosticFixes(doc, fixes) {
67
+ const acceptedEdits = [];
68
+ const appliedCodes = [];
69
+ for (const fix of fixes) {
70
+ const offsets = fix.edits.map((e) => {
71
+ const range = normalizeRange(e.range);
72
+ return {
73
+ start: doc.offsetAt(range.start),
74
+ end: doc.offsetAt(range.end),
75
+ newText: e.newText,
76
+ };
77
+ });
78
+ const overlaps = offsets.some((o) => acceptedEdits.some((a) => o.start < a.end && o.end > a.start));
79
+ if (overlaps) {
80
+ continue;
81
+ }
82
+ acceptedEdits.push(...offsets);
83
+ appliedCodes.push(fix.code);
84
+ }
85
+ acceptedEdits.sort((a, b) => a.start - b.start);
86
+ const text = doc.getText();
87
+ const spans = [];
88
+ let lastOffset = 0;
89
+ for (const edit of acceptedEdits) {
90
+ if (edit.start < lastOffset) {
91
+ continue;
92
+ }
93
+ if (edit.start > lastOffset) {
94
+ spans.push(text.substring(lastOffset, edit.start));
95
+ }
96
+ if (edit.newText.length) {
97
+ spans.push(edit.newText);
98
+ }
99
+ lastOffset = edit.end;
100
+ }
101
+ spans.push(text.substring(lastOffset));
102
+ return { text: spans.join(""), appliedCodes };
103
+ }
104
+ const MAX_FIX_ITERATIONS = 10;
105
+ export async function autofixMotoko(mocPath, files, mocArgs) {
106
+ const fixedFilesCodes = new Map();
107
+ for (let iteration = 0; iteration < MAX_FIX_ITERATIONS; iteration++) {
108
+ const fixesByFile = new Map();
109
+ for (const file of files) {
110
+ const result = await execa(mocPath, [file, ...mocArgs, "--error-format=json"], { stdio: "pipe", reject: false });
111
+ const diagnostics = parseDiagnostics(result.stdout);
112
+ for (const [targetFile, fixes] of extractDiagnosticFixes(diagnostics)) {
113
+ const existing = fixesByFile.get(targetFile) ?? [];
114
+ existing.push(...fixes);
115
+ fixesByFile.set(targetFile, existing);
116
+ }
117
+ }
118
+ if (fixesByFile.size === 0) {
119
+ break;
120
+ }
121
+ let progress = false;
122
+ for (const [file, fixes] of fixesByFile) {
123
+ const original = await readFile(file, "utf-8");
124
+ const doc = TextDocument.create(`file://${file}`, "motoko", 0, original);
125
+ const { text: result, appliedCodes } = applyDiagnosticFixes(doc, fixes);
126
+ if (result === original) {
127
+ continue;
128
+ }
129
+ await writeFile(file, result, "utf-8");
130
+ progress = true;
131
+ const existing = fixedFilesCodes.get(file) ?? [];
132
+ existing.push(...appliedCodes);
133
+ fixedFilesCodes.set(file, existing);
134
+ }
135
+ if (!progress) {
136
+ break;
137
+ }
138
+ }
139
+ if (fixedFilesCodes.size === 0) {
140
+ return null;
141
+ }
142
+ let totalFixCount = 0;
143
+ for (const codes of fixedFilesCodes.values()) {
144
+ totalFixCount += codes.length;
145
+ }
146
+ return {
147
+ fixedFiles: fixedFilesCodes,
148
+ totalFixCount,
149
+ };
150
+ }
@@ -1 +1,3 @@
1
+ import { type SemVer } from "semver";
2
+ export declare function getMocSemVer(): SemVer | null;
1
3
  export declare function getMocVersion(throwOnError?: boolean): string;
@@ -1,7 +1,16 @@
1
1
  import { execFileSync } from "node:child_process";
2
+ import { parse } from "semver";
3
+ import { readConfig } from "../mops.js";
2
4
  import { getMocPath } from "./get-moc-path.js";
5
+ export function getMocSemVer() {
6
+ return parse(getMocVersion(false));
7
+ }
3
8
  export function getMocVersion(throwOnError = false) {
4
- let mocPath = getMocPath(false);
9
+ let configVersion = readConfig().toolchain?.moc;
10
+ if (configVersion) {
11
+ return configVersion;
12
+ }
13
+ const mocPath = getMocPath(false);
5
14
  if (!mocPath) {
6
15
  return "";
7
16
  }
package/dist/mops.d.ts CHANGED
@@ -23,6 +23,7 @@ export declare function getGithubCommit(repo: string, ref: string): Promise<any>
23
23
  export declare function getDependencyType(version: string): "local" | "mops" | "github";
24
24
  export declare function parseDepValue(name: string, value: string): Dependency;
25
25
  export declare function readConfig(configFile?: string): Config;
26
+ export declare function getGlobalMocArgs(config: Config): string[];
26
27
  export declare function writeConfig(config: Config, configFile?: string): void;
27
28
  export declare function formatDir(name: string, version: string): string;
28
29
  export declare function formatGithubDir(name: string, repo: string): string;
package/dist/mops.js CHANGED
@@ -6,10 +6,12 @@ import chalk from "chalk";
6
6
  import prompts from "prompts";
7
7
  import fetch from "node-fetch";
8
8
  import { decodeFile } from "./pem.js";
9
+ import { cliError } from "./error.js";
9
10
  import { mainActor, storageActor } from "./api/actors.js";
10
11
  import { getNetwork } from "./api/network.js";
11
12
  import { getHighestVersion } from "./api/getHighestVersion.js";
12
13
  import { getPackageId } from "./helpers/get-package-id.js";
14
+ import { FILE_PATH_REGEX } from "./constants.js";
13
15
  if (!globalThis.fetch) {
14
16
  globalThis.fetch = fetch;
15
17
  }
@@ -135,7 +137,7 @@ export function getDependencyType(version) {
135
137
  if (version.startsWith("https://github.com/")) {
136
138
  return "github";
137
139
  }
138
- else if (version.match(/^(\.?\.)?\//)) {
140
+ else if (version.match(FILE_PATH_REGEX)) {
139
141
  return "local";
140
142
  }
141
143
  else {
@@ -176,6 +178,15 @@ export function readConfig(configFile = getClosestConfigFile()) {
176
178
  });
177
179
  return config;
178
180
  }
181
+ export function getGlobalMocArgs(config) {
182
+ if (!config.moc?.args) {
183
+ return [];
184
+ }
185
+ if (typeof config.moc.args === "string") {
186
+ cliError(`[moc] config 'args' should be an array of strings in mops.toml config file`);
187
+ }
188
+ return config.moc.args;
189
+ }
179
190
  export function writeConfig(config, configFile = getClosestConfigFile()) {
180
191
  let resConfig = JSON.parse(JSON.stringify(config));
181
192
  let deps = resConfig.dependencies || {};
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
@@ -71,7 +71,8 @@
71
71
  "stream-to-promise": "3.0.0",
72
72
  "string-width": "7.2.0",
73
73
  "tar": "7.5.6",
74
- "terminal-size": "4.0.0"
74
+ "terminal-size": "4.0.0",
75
+ "vscode-languageserver-textdocument": "1.0.12"
75
76
  },
76
77
  "devDependencies": {
77
78
  "@tsconfig/strictest": "2.0.5",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import { describe, test } from "@jest/globals";
2
+ import path from "path";
3
+ import { cliSnapshot } from "./helpers";
4
+ describe("build without dfx", () => {
5
+ test("builds using mops toolchain moc", async () => {
6
+ const cwd = path.join(import.meta.dirname, "build/no-dfx");
7
+ await cliSnapshot(["build"], { cwd }, 0);
8
+ }, 120_000);
9
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import path from "path";
3
+ import { cliSnapshot } from "./helpers";
4
+ describe("build", () => {
5
+ test("ok", async () => {
6
+ const cwd = path.join(import.meta.dirname, "build/success");
7
+ await cliSnapshot(["build", "--verbose"], { cwd }, 0);
8
+ await cliSnapshot(["build", "foo"], { cwd }, 0);
9
+ await cliSnapshot(["build", "bar"], { cwd }, 0);
10
+ await cliSnapshot(["build", "foo", "bar"], { cwd }, 0);
11
+ });
12
+ test("error", async () => {
13
+ const cwd = path.join(import.meta.dirname, "build/error");
14
+ await cliSnapshot(["build", "foo", "--verbose"], { cwd }, 0);
15
+ expect((await cliSnapshot(["build", "bar"], { cwd }, 1)).stderr).toMatch("Candid compatibility check failed for canister bar");
16
+ expect((await cliSnapshot(["build", "foo", "bar"], { cwd }, 1)).stderr).toMatch("Candid compatibility check failed for canister bar");
17
+ });
18
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { describe, test } from "@jest/globals";
2
+ import path from "path";
3
+ import { cliSnapshot } from "./helpers";
4
+ describe("check-candid", () => {
5
+ test("ok", async () => {
6
+ const cwd = path.join(import.meta.dirname, "check-candid");
7
+ await cliSnapshot(["check-candid", "a.did", "a.did"], { cwd }, 0);
8
+ await cliSnapshot(["check-candid", "b.did", "b.did"], { cwd }, 0);
9
+ await cliSnapshot(["check-candid", "c.did", "c.did"], { cwd }, 0);
10
+ await cliSnapshot(["check-candid", "a.did", "b.did"], { cwd }, 0);
11
+ await cliSnapshot(["check-candid", "b.did", "a.did"], { cwd }, 0);
12
+ });
13
+ test("error", async () => {
14
+ const cwd = path.join(import.meta.dirname, "check-candid");
15
+ await cliSnapshot(["check-candid", "a.did", "c.did"], { cwd }, 1);
16
+ await cliSnapshot(["check-candid", "c.did", "a.did"], { cwd }, 1);
17
+ await cliSnapshot(["check-candid", "b.did", "c.did"], { cwd }, 1);
18
+ await cliSnapshot(["check-candid", "c.did", "b.did"], { cwd }, 1);
19
+ });
20
+ });
@@ -0,0 +1 @@
1
+ export {};