diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 14483448..60dd0f8a 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -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 ` 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). diff --git a/cli/helpers/autofix-motoko.ts b/cli/helpers/autofix-motoko.ts index ee6992d1..7fbb6aee 100644 --- a/cli/helpers/autofix-motoko.ts +++ b/cli/helpers/autofix-motoko.ts @@ -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()) diff --git a/cli/tests/check-fix.test.ts b/cli/tests/check-fix.test.ts index f0ad6e27..e4793c90 100644 --- a/cli/tests/check-fix.test.ts +++ b/cli/tests/check-fix.test.ts @@ -14,6 +14,30 @@ function countCodes(stdout: string): Record { 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");