Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Mops CLI Changelog

## Next
- Fix `mops check --fix` crashing with `TypeError: Cannot read properties of undefined (reading 'split')` when `moc`'s diagnostic run resolves without buffered stdout. `parseDiagnostics` now treats a missing/`undefined` stdout as "no diagnostics" instead of calling `.split` on it, so the autofix pass degrades gracefully and the authoritative `mops check` still runs. Previously this unhandled throw aborted the whole command (and any deploy pipeline invoking `mops check --fix`).
- Fix `mops check --fix` and `mops lint --fix` corrupting source files when two `mops` processes run concurrently in the same project (e.g. two coding agents on the same checkout). Concurrent runs could apply stale `moc` byte offsets to a sibling's already-mutated file, leaving source like `let nat = identity` (with the type-arg and call dropped) or `list.sortInPlace(` with an unclosed paren. `--fix` invocations now acquire a project-root advisory lock at `.mops/fix.lock` and serialize, cargo-style ("Waiting for another `mops --fix` run to finish..."). Read-only `mops check` and `mops lint` are unchanged.

- Deprecate the `dfx` replica in `mops bench`, `mops test --mode replica`, and `mops watch`. Behavior is unchanged — `--replica dfx`, the implicit `dfx` fallback when no `[toolchain.pocket-ic]` is set, and the dfx-bundled PocketIC fallback all still work — but each now prints a warning. Run `mops toolchain use pocket-ic <version>` to silence it. The `dfx` paths will be removed and the default flipped to PocketIC in mops v3 — `dfx` is being deprecated upstream and PocketIC is a better fit for benchmarks and replica tests (deterministic, in-process, no background daemon).
Expand Down
5 changes: 4 additions & 1 deletion cli/helpers/autofix-motoko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export interface MocDiagnostic {
notes: string[];
}

export function parseDiagnostics(stdout: string): MocDiagnostic[] {
export function parseDiagnostics(stdout: string | undefined): MocDiagnostic[] {
if (!stdout) {
return [];
}
return stdout
.split("\n")
.filter((l) => l.trim())
Expand Down
24 changes: 24 additions & 0 deletions cli/tests/check-fix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@ function countCodes(stdout: string): Record<string, number> {
return counts;
}

describe("parseDiagnostics", () => {
test("returns [] for undefined stdout", () => {
// execa can resolve with `result.stdout === undefined` (e.g. output not
// buffered). The autofix path passes it straight in, so this must not throw.
expect(parseDiagnostics(undefined)).toEqual([]);
});

test("returns [] for empty stdout", () => {
expect(parseDiagnostics("")).toEqual([]);
});

test("skips non-JSON lines and parses diagnostics", () => {
const diag = {
message: "m",
code: "M0223",
level: "warning",
spans: [],
notes: [],
};
const stdout = `not json\n${JSON.stringify(diag)}\n`;
expect(parseDiagnostics(stdout)).toEqual([diag]);
});
});

describe("check --fix", () => {
const fixDir = path.join(import.meta.dirname, "check/fix");
const runDir = path.join(fixDir, "run");
Expand Down
Loading