ic-mops 2.14.0 → 2.14.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## Next
4
4
 
5
+ ## 2.14.1
6
+ - Speed up `mops check <files...>` (e.g. `mops check src/**/*.mo`) on packages with many files. Previously each file was checked in its own `moc` invocation, so every shared transitive import was re-parsed and re-type-checked once per file. All files are now passed to a single `moc --check` call, which loads and type-checks each import only once — on motoko-core (53 files) this drops a full check from ~27s to ~1.6s. The per-file `✓` confirmations now print only when the whole check passes.
7
+
5
8
  ## 2.14.0
6
9
  - Fix `mops check --fix` crashing with `TypeError: Cannot read properties of undefined (reading 'split')` when `moc` produces no output (e.g. it fails to spawn or is killed by the OOM killer in a memory-constrained container). The autofix pass now treats missing `moc` output as "no fixes to apply" and lets the regular check report the real failure, instead of aborting the whole command with an unhandled exception.
7
10
 
package/bundle/cli.tgz CHANGED
Binary file
package/commands/check.ts CHANGED
@@ -262,30 +262,30 @@ async function checkFiles(
262
262
  logAutofixResult(fixResult, options.verbose);
263
263
  }
264
264
 
265
- for (const file of files) {
266
- try {
267
- const args = [file, ...mocArgs];
268
- if (options.verbose) {
269
- console.log(chalk.blue("check"), chalk.gray("Running moc:"));
270
- console.log(chalk.gray(mocPath, JSON.stringify(args)));
271
- }
272
-
273
- const result = await execa(mocPath, args, {
274
- stdio: "inherit",
275
- reject: false,
276
- });
265
+ // Check all files in a single moc invocation so shared transitive imports
266
+ // are chased and type-checked once, not re-checked for every file.
267
+ const args = [...files, ...mocArgs];
268
+ if (options.verbose) {
269
+ console.log(chalk.blue("check"), chalk.gray("Running moc:"));
270
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
271
+ }
277
272
 
278
- if (result.exitCode !== 0) {
279
- cliError(
280
- `✗ Check failed for file ${file} (exit code: ${result.exitCode})`,
281
- );
282
- }
273
+ try {
274
+ const result = await execa(mocPath, args, {
275
+ stdio: "inherit",
276
+ reject: false,
277
+ });
283
278
 
284
- console.log(chalk.green(`✓ ${file}`));
285
- } catch (err: any) {
286
- cliError(
287
- `Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`,
288
- );
279
+ if (result.exitCode !== 0) {
280
+ cliError(`✗ Check failed (exit code: ${result.exitCode})`);
289
281
  }
282
+ } catch (err: any) {
283
+ cliError(
284
+ `Error while checking files${err?.message ? `\n${err.message}` : ""}`,
285
+ );
286
+ }
287
+
288
+ for (const file of files) {
289
+ console.log(chalk.green(`✓ ${file}`));
290
290
  }
291
291
  }
@@ -171,24 +171,26 @@ async function checkFiles(config, files, options) {
171
171
  const fixResult = await autofixMotoko(mocPath, files, mocArgs);
172
172
  logAutofixResult(fixResult, options.verbose);
173
173
  }
174
- for (const file of files) {
175
- try {
176
- const args = [file, ...mocArgs];
177
- if (options.verbose) {
178
- console.log(chalk.blue("check"), chalk.gray("Running moc:"));
179
- console.log(chalk.gray(mocPath, JSON.stringify(args)));
180
- }
181
- const result = await execa(mocPath, args, {
182
- stdio: "inherit",
183
- reject: false,
184
- });
185
- if (result.exitCode !== 0) {
186
- cliError(`✗ Check failed for file ${file} (exit code: ${result.exitCode})`);
187
- }
188
- console.log(chalk.green(`✓ ${file}`));
189
- }
190
- catch (err) {
191
- cliError(`Error while checking ${file}${err?.message ? `\n${err.message}` : ""}`);
174
+ // Check all files in a single moc invocation so shared transitive imports
175
+ // are chased and type-checked once, not re-checked for every file.
176
+ const args = [...files, ...mocArgs];
177
+ if (options.verbose) {
178
+ console.log(chalk.blue("check"), chalk.gray("Running moc:"));
179
+ console.log(chalk.gray(mocPath, JSON.stringify(args)));
180
+ }
181
+ try {
182
+ const result = await execa(mocPath, args, {
183
+ stdio: "inherit",
184
+ reject: false,
185
+ });
186
+ if (result.exitCode !== 0) {
187
+ cliError(`✗ Check failed (exit code: ${result.exitCode})`);
192
188
  }
193
189
  }
190
+ catch (err) {
191
+ cliError(`Error while checking files${err?.message ? `\n${err.message}` : ""}`);
192
+ }
193
+ for (const file of files) {
194
+ console.log(chalk.green(`✓ ${file}`));
195
+ }
194
196
  }
@@ -109,14 +109,13 @@ export async function autofixMotoko(mocPath, files, mocArgs) {
109
109
  const fixedFilesCodes = new Map();
110
110
  for (let iteration = 0; iteration < MAX_FIX_ITERATIONS; iteration++) {
111
111
  const fixesByFile = new Map();
112
- for (const file of files) {
113
- const result = await execa(mocPath, [file, ...mocArgs, "--error-format=json"], { stdio: "pipe", reject: false });
114
- const diagnostics = parseDiagnostics(result.stdout);
115
- for (const [targetFile, fixes] of extractDiagnosticFixes(diagnostics)) {
116
- const existing = fixesByFile.get(targetFile) ?? [];
117
- existing.push(...fixes);
118
- fixesByFile.set(targetFile, existing);
119
- }
112
+ // Single invocation: moc dedups shared imports across all files.
113
+ const result = await execa(mocPath, [...files, ...mocArgs, "--error-format=json"], { stdio: "pipe", reject: false });
114
+ const diagnostics = parseDiagnostics(result.stdout);
115
+ for (const [targetFile, fixes] of extractDiagnosticFixes(diagnostics)) {
116
+ const existing = fixesByFile.get(targetFile) ?? [];
117
+ existing.push(...fixes);
118
+ fixesByFile.set(targetFile, existing);
120
119
  }
121
120
  if (fixesByFile.size === 0) {
122
121
  break;
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.14.0",
3
+ "version": "2.14.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "bin/mops.js",
@@ -12,6 +12,12 @@ describe("check", () => {
12
12
  await cliSnapshot(["check", "Error.mo"], { cwd }, 1);
13
13
  await cliSnapshot(["check", "Ok.mo", "Error.mo"], { cwd }, 1);
14
14
  });
15
+ // The verbose snapshot shows a single "moc ... [both files]" line, proving the
16
+ // whole set is checked in one invocation rather than one moc call per file.
17
+ test("multiple files in a single invocation", async () => {
18
+ const cwd = path.join(import.meta.dirname, "check/success");
19
+ await cliSnapshot(["check", "Ok.mo", "Warning.mo", "--verbose"], { cwd }, 0);
20
+ });
15
21
  test("warning", async () => {
16
22
  const cwd = path.join(import.meta.dirname, "check/success");
17
23
  const result = await cliSnapshot(["check", "Warning.mo"], { cwd }, 0);
@@ -184,19 +184,18 @@ export async function autofixMotoko(
184
184
  for (let iteration = 0; iteration < MAX_FIX_ITERATIONS; iteration++) {
185
185
  const fixesByFile = new Map<string, DiagnosticFix[]>();
186
186
 
187
- for (const file of files) {
188
- const result = await execa(
189
- mocPath,
190
- [file, ...mocArgs, "--error-format=json"],
191
- { stdio: "pipe", reject: false },
192
- );
193
-
194
- const diagnostics = parseDiagnostics(result.stdout);
195
- for (const [targetFile, fixes] of extractDiagnosticFixes(diagnostics)) {
196
- const existing = fixesByFile.get(targetFile) ?? [];
197
- existing.push(...fixes);
198
- fixesByFile.set(targetFile, existing);
199
- }
187
+ // Single invocation: moc dedups shared imports across all files.
188
+ const result = await execa(
189
+ mocPath,
190
+ [...files, ...mocArgs, "--error-format=json"],
191
+ { stdio: "pipe", reject: false },
192
+ );
193
+
194
+ const diagnostics = parseDiagnostics(result.stdout);
195
+ for (const [targetFile, fixes] of extractDiagnosticFixes(diagnostics)) {
196
+ const existing = fixesByFile.get(targetFile) ?? [];
197
+ existing.push(...fixes);
198
+ fixesByFile.set(targetFile, existing);
200
199
  }
201
200
 
202
201
  if (fixesByFile.size === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ic-mops",
3
- "version": "2.14.0",
3
+ "version": "2.14.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mops": "dist/bin/mops.js",
@@ -5,7 +5,7 @@ exports[`check [moc] args are passed to moc 1`] = `
5
5
  "exitCode": 1,
6
6
  "stderr": "Warning.mo:3.9-3.15: warning [M0194], unused identifier: \`unused\`
7
7
  help: if this is intentional, prefix it with an underscore: \`_unused\`
8
- ✗ Check failed for file Warning.mo (exit code: 1)",
8
+ ✗ Check failed (exit code: 1)",
9
9
  "stdout": "",
10
10
  }
11
11
  `;
@@ -18,7 +18,7 @@ exports[`check error 1`] = `
18
18
  cannot produce expected type
19
19
  ()
20
20
  Error.mo:7.1-7.21: type error [M0057], unbound variable thisshouldnotcompile
21
- ✗ Check failed for file Error.mo (exit code: 1)",
21
+ ✗ Check failed (exit code: 1)",
22
22
  "stdout": "",
23
23
  }
24
24
  `;
@@ -27,11 +27,24 @@ exports[`check error 2`] = `
27
27
  {
28
28
  "exitCode": 1,
29
29
  "stderr": "Ok.mo: No such file or directory
30
- ✗ Check failed for file Ok.mo (exit code: 1)",
30
+ ✗ Check failed (exit code: 1)",
31
31
  "stdout": "",
32
32
  }
33
33
  `;
34
34
 
35
+ exports[`check multiple files in a single invocation 1`] = `
36
+ {
37
+ "exitCode": 0,
38
+ "stderr": "Warning.mo:3.9-3.15: warning [M0194], unused identifier: \`unused\`
39
+ help: if this is intentional, prefix it with an underscore: \`_unused\`",
40
+ "stdout": "check Using --all-libs for richer diagnostics
41
+ check Running moc:
42
+ <CACHE>moc-wrapper ["Ok.mo","Warning.mo","--check","--all-libs","--default-persistent-actors"]
43
+ ✓ Ok.mo
44
+ ✓ Warning.mo",
45
+ }
46
+ `;
47
+
35
48
  exports[`check no args checks all canisters 1`] = `
36
49
  {
37
50
  "exitCode": 0,
@@ -85,7 +98,7 @@ exports[`check warning with -Werror flag 1`] = `
85
98
  "exitCode": 1,
86
99
  "stderr": "Warning.mo:3.9-3.15: warning [M0194], unused identifier: \`unused\`
87
100
  help: if this is intentional, prefix it with an underscore: \`_unused\`
88
- ✗ Check failed for file Warning.mo (exit code: 1)",
101
+ ✗ Check failed (exit code: 1)",
89
102
  "stdout": "",
90
103
  }
91
104
  `;
@@ -15,6 +15,17 @@ describe("check", () => {
15
15
  await cliSnapshot(["check", "Ok.mo", "Error.mo"], { cwd }, 1);
16
16
  });
17
17
 
18
+ // The verbose snapshot shows a single "moc ... [both files]" line, proving the
19
+ // whole set is checked in one invocation rather than one moc call per file.
20
+ test("multiple files in a single invocation", async () => {
21
+ const cwd = path.join(import.meta.dirname, "check/success");
22
+ await cliSnapshot(
23
+ ["check", "Ok.mo", "Warning.mo", "--verbose"],
24
+ { cwd },
25
+ 0,
26
+ );
27
+ });
28
+
18
29
  test("warning", async () => {
19
30
  const cwd = path.join(import.meta.dirname, "check/success");
20
31
  const result = await cliSnapshot(["check", "Warning.mo"], { cwd }, 0);