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.
- package/CHANGELOG.md +18 -0
- package/RELEASE.md +198 -0
- package/bundle/cli.tgz +0 -0
- package/check-requirements.ts +3 -8
- package/cli.ts +94 -11
- package/commands/bench/bench-canister.mo +17 -6
- package/commands/bench.ts +13 -16
- package/commands/build.ts +5 -6
- package/commands/check.ts +121 -0
- package/commands/format.ts +3 -18
- package/commands/lint.ts +92 -0
- package/commands/sync.ts +2 -8
- package/commands/test/test.ts +10 -20
- package/commands/toolchain/index.ts +21 -8
- package/commands/toolchain/lintoko.ts +54 -0
- package/commands/toolchain/toolchain-utils.ts +2 -0
- package/commands/watch/error-checker.ts +8 -2
- package/commands/watch/warning-checker.ts +8 -2
- package/constants.ts +23 -0
- package/dist/check-requirements.js +3 -8
- package/dist/cli.js +73 -11
- package/dist/commands/bench/bench-canister.mo +17 -6
- package/dist/commands/bench.js +7 -15
- package/dist/commands/build.js +5 -6
- package/dist/commands/check.d.ts +6 -0
- package/dist/commands/check.js +82 -0
- package/dist/commands/format.js +3 -16
- package/dist/commands/lint.d.ts +7 -0
- package/dist/commands/lint.js +69 -0
- package/dist/commands/sync.js +2 -7
- package/dist/commands/test/test.js +10 -18
- package/dist/commands/toolchain/index.d.ts +2 -2
- package/dist/commands/toolchain/index.js +18 -7
- package/dist/commands/toolchain/lintoko.d.ts +8 -0
- package/dist/commands/toolchain/lintoko.js +36 -0
- package/dist/commands/toolchain/toolchain-utils.d.ts +1 -0
- package/dist/commands/toolchain/toolchain-utils.js +1 -0
- package/dist/commands/watch/error-checker.js +8 -2
- package/dist/commands/watch/warning-checker.js +8 -2
- package/dist/constants.d.ts +15 -0
- package/dist/constants.js +21 -0
- package/dist/environments/nodejs/cli.js +6 -1
- package/dist/error.d.ts +1 -1
- package/dist/helpers/autofix-motoko.d.ts +26 -0
- package/dist/helpers/autofix-motoko.js +150 -0
- package/dist/helpers/get-moc-version.d.ts +2 -0
- package/dist/helpers/get-moc-version.js +10 -1
- package/dist/mops.d.ts +1 -0
- package/dist/mops.js +12 -1
- package/dist/package.json +3 -2
- package/dist/tests/build-no-dfx.test.d.ts +1 -0
- package/dist/tests/build-no-dfx.test.js +9 -0
- package/dist/tests/build.test.d.ts +1 -0
- package/dist/tests/build.test.js +18 -0
- package/dist/tests/check-candid.test.d.ts +1 -0
- package/dist/tests/check-candid.test.js +20 -0
- package/dist/tests/check-fix.test.d.ts +1 -0
- package/dist/tests/check-fix.test.js +89 -0
- package/dist/tests/check.test.d.ts +1 -0
- package/dist/tests/check.test.js +37 -0
- package/dist/tests/cli.test.js +4 -57
- package/dist/tests/helpers.d.ts +22 -0
- package/dist/tests/helpers.js +43 -0
- package/dist/tests/lint.test.d.ts +1 -0
- package/dist/tests/lint.test.js +15 -0
- package/dist/tests/moc-args.test.d.ts +1 -0
- package/dist/tests/moc-args.test.js +17 -0
- package/dist/tests/toolchain.test.d.ts +1 -0
- package/dist/tests/toolchain.test.js +11 -0
- package/dist/types.d.ts +8 -1
- package/dist/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/environments/nodejs/cli.ts +7 -1
- package/error.ts +1 -1
- package/helpers/autofix-motoko.ts +240 -0
- package/helpers/get-moc-version.ts +12 -1
- package/mops.ts +15 -1
- package/package.json +3 -2
- package/tests/__snapshots__/build-no-dfx.test.ts.snap +11 -0
- package/tests/__snapshots__/build.test.ts.snap +77 -0
- package/tests/__snapshots__/check-candid.test.ts.snap +73 -0
- package/tests/__snapshots__/check-fix.test.ts.snap +261 -0
- package/tests/__snapshots__/check.test.ts.snap +81 -0
- package/tests/__snapshots__/lint.test.ts.snap +78 -0
- package/tests/build/no-dfx/mops.toml +5 -0
- package/tests/build/no-dfx/src/Main.mo +5 -0
- package/tests/build-no-dfx.test.ts +10 -0
- package/tests/build.test.ts +24 -0
- package/tests/check/error/Error.mo +7 -0
- package/tests/check/error/mops.toml +2 -0
- package/tests/check/fix/M0223.mo +11 -0
- package/tests/check/fix/M0236.mo +11 -0
- package/tests/check/fix/M0237.mo +11 -0
- package/tests/check/fix/Ok.mo +7 -0
- package/tests/check/fix/edit-suggestions.mo +143 -0
- package/tests/check/fix/mops.toml +5 -0
- package/tests/check/fix/overlapping.mo +10 -0
- package/tests/check/fix/transitive-lib.mo +9 -0
- package/tests/check/fix/transitive-main.mo +9 -0
- package/tests/check/moc-args/Warning.mo +5 -0
- package/tests/check/moc-args/mops.toml +2 -0
- package/tests/check/success/Ok.mo +5 -0
- package/tests/check/success/Warning.mo +5 -0
- package/tests/check/success/mops.toml +2 -0
- package/tests/check-candid.test.ts +22 -0
- package/tests/check-fix.test.ts +134 -0
- package/tests/check.test.ts +51 -0
- package/tests/cli.test.ts +4 -74
- package/tests/helpers.ts +58 -0
- package/tests/lint/lints/no-bool-switch.toml +9 -0
- package/tests/lint/mops.toml +4 -0
- package/tests/lint/src/NoBoolSwitch.mo +8 -0
- package/tests/lint/src/Ok.mo +5 -0
- package/tests/lint.test.ts +17 -0
- package/tests/moc-args.test.ts +19 -0
- package/tests/toolchain/mock +2 -0
- package/tests/toolchain/mops.toml +2 -0
- package/tests/toolchain.test.ts +12 -0
- package/types.ts +8 -1
- package/wasm/Cargo.lock +101 -54
- package/wasm/pkg/nodejs/wasm_bg.wasm +0 -0
- package/wasm/pkg/web/wasm_bg.wasm +0 -0
- package/.DS_Store +0 -0
- package/bundle/bench/bench-canister.mo +0 -121
- package/bundle/bench/user-bench.mo +0 -10
- package/bundle/bin/moc-wrapper.sh +0 -40
- package/bundle/bin/mops.js +0 -3
- package/bundle/cli.js +0 -2144
- package/bundle/declarations/bench/bench.did +0 -30
- package/bundle/declarations/bench/bench.did.d.ts +0 -33
- package/bundle/declarations/bench/bench.did.js +0 -30
- package/bundle/declarations/bench/index.d.ts +0 -50
- package/bundle/declarations/bench/index.js +0 -40
- package/bundle/declarations/main/index.d.ts +0 -50
- package/bundle/declarations/main/index.js +0 -40
- package/bundle/declarations/main/main.did +0 -428
- package/bundle/declarations/main/main.did.d.ts +0 -348
- package/bundle/declarations/main/main.did.js +0 -406
- package/bundle/declarations/storage/index.d.ts +0 -50
- package/bundle/declarations/storage/index.js +0 -30
- package/bundle/declarations/storage/storage.did +0 -46
- package/bundle/declarations/storage/storage.did.d.ts +0 -40
- package/bundle/declarations/storage/storage.did.js +0 -38
- package/bundle/package.json +0 -36
- package/bundle/templates/README.md +0 -13
- package/bundle/templates/licenses/Apache-2.0 +0 -202
- package/bundle/templates/licenses/Apache-2.0-NOTICE +0 -13
- package/bundle/templates/licenses/MIT +0 -21
- package/bundle/templates/mops-publish.yml +0 -17
- package/bundle/templates/mops-test.yml +0 -24
- package/bundle/templates/src/lib.mo +0 -15
- package/bundle/templates/test/lib.test.mo +0 -4
- package/bundle/wasm_bg.wasm +0 -0
- package/bundle/xhr-sync-worker.js +0 -59
- package/dist/wasm/pkg/bundler/package.json +0 -20
- package/dist/wasm/pkg/bundler/wasm.d.ts +0 -3
- package/dist/wasm/pkg/bundler/wasm.js +0 -5
- package/dist/wasm/pkg/bundler/wasm_bg.js +0 -93
- package/dist/wasm/pkg/bundler/wasm_bg.wasm +0 -0
- package/dist/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
- package/tests/__snapshots__/cli.test.ts.snap +0 -202
- package/tests/build/success/.dfx/local/canister_ids.json +0 -17
- package/tests/build/success/.dfx/local/canisters/bar/bar.did +0 -3
- package/tests/build/success/.dfx/local/canisters/bar/bar.most +0 -4
- package/tests/build/success/.dfx/local/canisters/bar/bar.wasm +0 -0
- package/tests/build/success/.dfx/local/canisters/bar/constructor.did +0 -3
- package/tests/build/success/.dfx/local/canisters/bar/index.js +0 -42
- package/tests/build/success/.dfx/local/canisters/bar/init_args.txt +0 -1
- package/tests/build/success/.dfx/local/canisters/bar/service.did +0 -3
- package/tests/build/success/.dfx/local/canisters/bar/service.did.d.ts +0 -7
- package/tests/build/success/.dfx/local/canisters/bar/service.did.js +0 -4
- package/tests/build/success/.dfx/local/canisters/foo/constructor.did +0 -3
- package/tests/build/success/.dfx/local/canisters/foo/foo.did +0 -3
- package/tests/build/success/.dfx/local/canisters/foo/foo.most +0 -4
- package/tests/build/success/.dfx/local/canisters/foo/foo.wasm +0 -0
- package/tests/build/success/.dfx/local/canisters/foo/index.js +0 -42
- package/tests/build/success/.dfx/local/canisters/foo/init_args.txt +0 -1
- package/tests/build/success/.dfx/local/canisters/foo/service.did +0 -3
- package/tests/build/success/.dfx/local/canisters/foo/service.did.d.ts +0 -7
- package/tests/build/success/.dfx/local/canisters/foo/service.did.js +0 -4
- package/tests/build/success/.dfx/local/lsp/ucwa4-rx777-77774-qaada-cai.did +0 -3
- package/tests/build/success/.dfx/local/lsp/ulvla-h7777-77774-qaacq-cai.did +0 -3
- package/tests/build/success/.dfx/local/network-id +0 -4
- package/wasm/pkg/bundler/package.json +0 -20
- package/wasm/pkg/bundler/wasm.d.ts +0 -3
- package/wasm/pkg/bundler/wasm.js +0 -5
- package/wasm/pkg/bundler/wasm_bg.js +0 -93
- package/wasm/pkg/bundler/wasm_bg.wasm +0 -0
- package/wasm/pkg/bundler/wasm_bg.wasm.d.ts +0 -8
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { beforeAll, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { cpSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { parseDiagnostics } from "../helpers/autofix-motoko";
|
|
5
|
+
import { cli, normalizePaths } from "./helpers";
|
|
6
|
+
function countCodes(stdout) {
|
|
7
|
+
const counts = {};
|
|
8
|
+
for (const diag of parseDiagnostics(stdout)) {
|
|
9
|
+
counts[diag.code] = (counts[diag.code] ?? 0) + 1;
|
|
10
|
+
}
|
|
11
|
+
return counts;
|
|
12
|
+
}
|
|
13
|
+
describe("check --fix", () => {
|
|
14
|
+
const fixDir = path.join(import.meta.dirname, "check/fix");
|
|
15
|
+
const runDir = path.join(fixDir, "run");
|
|
16
|
+
const warningFlags = "-W=M0223,M0236,M0237";
|
|
17
|
+
const diagnosticFlags = [warningFlags, "--error-format=json"];
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
for (const file of readdirSync(runDir).filter((f) => f.endsWith(".mo"))) {
|
|
20
|
+
unlinkSync(path.join(runDir, file));
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
function copyFixture(file) {
|
|
24
|
+
const dest = path.join(runDir, file);
|
|
25
|
+
cpSync(path.join(fixDir, file), dest);
|
|
26
|
+
return dest;
|
|
27
|
+
}
|
|
28
|
+
async function testCheckFix(file, expectedDiagnostics, expectedAfterDiagnostics = {}) {
|
|
29
|
+
const runFilePath = copyFixture(file);
|
|
30
|
+
const beforeResult = await cli(["check", runFilePath, "--", ...diagnosticFlags], { cwd: fixDir });
|
|
31
|
+
expect(countCodes(beforeResult.stdout)).toEqual(expectedDiagnostics);
|
|
32
|
+
const fixResult = await cli(["check", runFilePath, "--fix", "--", warningFlags], { cwd: fixDir });
|
|
33
|
+
expect(normalizePaths(fixResult.stdout)).toMatchSnapshot("fix output");
|
|
34
|
+
expect(readFileSync(runFilePath, "utf-8")).toMatchSnapshot();
|
|
35
|
+
const afterResult = await cli(["check", runFilePath, "--", ...diagnosticFlags], { cwd: fixDir });
|
|
36
|
+
expect(countCodes(afterResult.stdout)).toEqual(expectedAfterDiagnostics);
|
|
37
|
+
return runFilePath;
|
|
38
|
+
}
|
|
39
|
+
test("M0223", async () => {
|
|
40
|
+
await testCheckFix("M0223.mo", { M0223: 1 });
|
|
41
|
+
});
|
|
42
|
+
test("M0236", async () => {
|
|
43
|
+
await testCheckFix("M0236.mo", { M0236: 1 });
|
|
44
|
+
});
|
|
45
|
+
test("M0237", async () => {
|
|
46
|
+
await testCheckFix("M0237.mo", { M0237: 1 });
|
|
47
|
+
});
|
|
48
|
+
test("edit-suggestions", async () => {
|
|
49
|
+
await testCheckFix("edit-suggestions.mo", {
|
|
50
|
+
M0223: 2,
|
|
51
|
+
M0236: 11,
|
|
52
|
+
M0237: 17,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
test("overlapping edits", async () => {
|
|
56
|
+
await testCheckFix("overlapping.mo", { M0223: 1, M0236: 2 });
|
|
57
|
+
});
|
|
58
|
+
test("transitive imports", async () => {
|
|
59
|
+
const runMainPath = copyFixture("transitive-main.mo");
|
|
60
|
+
const runLibPath = copyFixture("transitive-lib.mo");
|
|
61
|
+
const fixResult = await cli(["check", runMainPath, "--fix", "--", warningFlags], { cwd: fixDir });
|
|
62
|
+
expect(normalizePaths(fixResult.stdout)).toMatchSnapshot("fix output");
|
|
63
|
+
expect(readFileSync(runMainPath, "utf-8")).toMatchSnapshot("main file");
|
|
64
|
+
expect(readFileSync(runLibPath, "utf-8")).toMatchSnapshot("lib file");
|
|
65
|
+
const afterResult = await cli(["check", runMainPath, "--", ...diagnosticFlags], { cwd: fixDir });
|
|
66
|
+
expect(countCodes(afterResult.stdout)).toEqual({});
|
|
67
|
+
});
|
|
68
|
+
test("--error-format=human does not break --fix", async () => {
|
|
69
|
+
const runFilePath = copyFixture("M0223.mo");
|
|
70
|
+
const fixResult = await cli([
|
|
71
|
+
"check",
|
|
72
|
+
runFilePath,
|
|
73
|
+
"--fix",
|
|
74
|
+
"--",
|
|
75
|
+
warningFlags,
|
|
76
|
+
"--error-format=human",
|
|
77
|
+
], { cwd: fixDir });
|
|
78
|
+
expect(fixResult.stdout).toContain("1 fix applied");
|
|
79
|
+
expect(readFileSync(runFilePath, "utf-8")).not.toContain("<Nat>");
|
|
80
|
+
});
|
|
81
|
+
test("verbose", async () => {
|
|
82
|
+
const result = await cli(["check", "Ok.mo", "--fix", "--verbose"], {
|
|
83
|
+
cwd: fixDir,
|
|
84
|
+
});
|
|
85
|
+
expect(result.exitCode).toBe(0);
|
|
86
|
+
expect(result.stdout).toContain("Attempting to fix files");
|
|
87
|
+
expect(result.stdout).toContain("No fixes were needed");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { cliSnapshot } from "./helpers";
|
|
4
|
+
describe("check", () => {
|
|
5
|
+
test("ok", async () => {
|
|
6
|
+
const cwd = path.join(import.meta.dirname, "check/success");
|
|
7
|
+
await cliSnapshot(["check", "Ok.mo"], { cwd }, 0);
|
|
8
|
+
await cliSnapshot(["check", "Ok.mo", "--verbose"], { cwd }, 0);
|
|
9
|
+
});
|
|
10
|
+
test("error", async () => {
|
|
11
|
+
const cwd = path.join(import.meta.dirname, "check/error");
|
|
12
|
+
await cliSnapshot(["check", "Error.mo"], { cwd }, 1);
|
|
13
|
+
await cliSnapshot(["check", "Ok.mo", "Error.mo"], { cwd }, 1);
|
|
14
|
+
});
|
|
15
|
+
test("warning", async () => {
|
|
16
|
+
const cwd = path.join(import.meta.dirname, "check/success");
|
|
17
|
+
const result = await cliSnapshot(["check", "Warning.mo"], { cwd }, 0);
|
|
18
|
+
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
19
|
+
expect(result.stderr).toMatch(/unused identifier/);
|
|
20
|
+
});
|
|
21
|
+
test("warning verbose", async () => {
|
|
22
|
+
const cwd = path.join(import.meta.dirname, "check/success");
|
|
23
|
+
const result = await cliSnapshot(["check", "Warning.mo", "--verbose"], { cwd }, 0);
|
|
24
|
+
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
25
|
+
expect(result.stderr).toMatch(/unused identifier/);
|
|
26
|
+
});
|
|
27
|
+
test("warning with -Werror flag", async () => {
|
|
28
|
+
const cwd = path.join(import.meta.dirname, "check/success");
|
|
29
|
+
const result = await cliSnapshot(["check", "Warning.mo", "--", "-Werror"], { cwd }, 1);
|
|
30
|
+
expect(result.stderr).toMatch(/warning \[M0194\]/);
|
|
31
|
+
expect(result.stderr).toMatch(/unused identifier/);
|
|
32
|
+
});
|
|
33
|
+
test("[moc] args are passed to moc", async () => {
|
|
34
|
+
const cwd = path.join(import.meta.dirname, "check/moc-args");
|
|
35
|
+
await cliSnapshot(["check", "Warning.mo"], { cwd }, 1);
|
|
36
|
+
});
|
|
37
|
+
});
|
package/dist/tests/cli.test.js
CHANGED
|
@@ -1,63 +1,10 @@
|
|
|
1
1
|
import { describe, expect, test } from "@jest/globals";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return await execa("npm", ["run", "mops", "--", ...args], {
|
|
6
|
-
env: { MOPS_CWD: cwd },
|
|
7
|
-
stdio: "pipe",
|
|
8
|
-
reject: false,
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
const cliSnapshot = async (args, options, exitCode) => {
|
|
12
|
-
const result = await cli(args, options);
|
|
13
|
-
expect({
|
|
14
|
-
command: result.command,
|
|
15
|
-
exitCode: result.exitCode,
|
|
16
|
-
timedOut: result.timedOut,
|
|
17
|
-
stdio: Boolean(result.stdout || result.stderr),
|
|
18
|
-
}).toEqual({
|
|
19
|
-
command: result.command,
|
|
20
|
-
exitCode,
|
|
21
|
-
timedOut: false,
|
|
22
|
-
stdio: true,
|
|
23
|
-
});
|
|
24
|
-
expect({
|
|
25
|
-
exitCode: result.exitCode,
|
|
26
|
-
stdout: result.stdout,
|
|
27
|
-
stderr: result.stderr,
|
|
28
|
-
}).toMatchSnapshot();
|
|
29
|
-
return result;
|
|
30
|
-
};
|
|
31
|
-
describe("mops", () => {
|
|
32
|
-
test("version", async () => {
|
|
2
|
+
import { cli } from "./helpers";
|
|
3
|
+
describe("cli", () => {
|
|
4
|
+
test("--version", async () => {
|
|
33
5
|
expect((await cli(["--version"])).stdout).toMatch(/CLI \d+\.\d+\.\d+/);
|
|
34
6
|
});
|
|
35
|
-
test("help", async () => {
|
|
7
|
+
test("--help", async () => {
|
|
36
8
|
expect((await cli(["--help"])).stdout).toMatch(/^Usage: mops/m);
|
|
37
9
|
});
|
|
38
|
-
test("build success", async () => {
|
|
39
|
-
const cwd = path.join(import.meta.dirname, "build/success");
|
|
40
|
-
await cliSnapshot(["build"], { cwd }, 0);
|
|
41
|
-
await cliSnapshot(["build", "foo"], { cwd }, 0);
|
|
42
|
-
await cliSnapshot(["build", "bar"], { cwd }, 0);
|
|
43
|
-
await cliSnapshot(["build", "foo", "bar"], { cwd }, 0);
|
|
44
|
-
});
|
|
45
|
-
test("build error", async () => {
|
|
46
|
-
const cwd = path.join(import.meta.dirname, "build/error");
|
|
47
|
-
await cliSnapshot(["build", "foo"], { cwd }, 0);
|
|
48
|
-
expect((await cliSnapshot(["build", "bar"], { cwd }, 1)).stderr).toMatch("Candid compatibility check failed for canister bar");
|
|
49
|
-
expect((await cliSnapshot(["build", "foo", "bar"], { cwd }, 1)).stderr).toMatch("Candid compatibility check failed for canister bar");
|
|
50
|
-
});
|
|
51
|
-
test("check-candid", async () => {
|
|
52
|
-
const cwd = path.join(import.meta.dirname, "check-candid");
|
|
53
|
-
await cliSnapshot(["check-candid", "a.did", "a.did"], { cwd }, 0);
|
|
54
|
-
await cliSnapshot(["check-candid", "b.did", "b.did"], { cwd }, 0);
|
|
55
|
-
await cliSnapshot(["check-candid", "c.did", "c.did"], { cwd }, 0);
|
|
56
|
-
await cliSnapshot(["check-candid", "a.did", "b.did"], { cwd }, 0);
|
|
57
|
-
await cliSnapshot(["check-candid", "b.did", "a.did"], { cwd }, 0);
|
|
58
|
-
await cliSnapshot(["check-candid", "a.did", "c.did"], { cwd }, 1);
|
|
59
|
-
await cliSnapshot(["check-candid", "c.did", "a.did"], { cwd }, 1);
|
|
60
|
-
await cliSnapshot(["check-candid", "b.did", "c.did"], { cwd }, 1);
|
|
61
|
-
await cliSnapshot(["check-candid", "c.did", "b.did"], { cwd }, 1);
|
|
62
|
-
});
|
|
63
10
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface CliOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
}
|
|
4
|
+
export declare const cli: (args: string[], { cwd }?: CliOptions) => Promise<import("execa").Result<{
|
|
5
|
+
stdio: "pipe";
|
|
6
|
+
reject: false;
|
|
7
|
+
cwd?: string | undefined;
|
|
8
|
+
env: {
|
|
9
|
+
MOPS_CWD?: string | undefined;
|
|
10
|
+
TZ?: string;
|
|
11
|
+
};
|
|
12
|
+
}>>;
|
|
13
|
+
export declare const normalizePaths: (text: string) => string;
|
|
14
|
+
export declare const cliSnapshot: (args: string[], options: CliOptions, exitCode: number) => Promise<import("execa").Result<{
|
|
15
|
+
stdio: "pipe";
|
|
16
|
+
reject: false;
|
|
17
|
+
cwd?: string | undefined;
|
|
18
|
+
env: {
|
|
19
|
+
MOPS_CWD?: string | undefined;
|
|
20
|
+
TZ?: string;
|
|
21
|
+
};
|
|
22
|
+
}>>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expect } from "@jest/globals";
|
|
2
|
+
import { execa } from "execa";
|
|
3
|
+
import { dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
export const cli = async (args, { cwd } = {}) => {
|
|
6
|
+
return await execa("npm", ["run", "--silent", "mops", "--", ...args], {
|
|
7
|
+
env: { ...process.env, ...(cwd != null && { MOPS_CWD: cwd }) },
|
|
8
|
+
...(cwd != null && { cwd }),
|
|
9
|
+
stdio: "pipe",
|
|
10
|
+
reject: false,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
// Strip ANSI escape codes for portable snapshots (avoid control char in regex literal)
|
|
14
|
+
const stripAnsi = (s) => s.replace(new RegExp(`\u001b\\[[0-9;]*m`, "g"), "");
|
|
15
|
+
export const normalizePaths = (text) => {
|
|
16
|
+
// Replace absolute paths with placeholders for CI
|
|
17
|
+
return stripAnsi(text
|
|
18
|
+
.replaceAll(dirname(fileURLToPath(import.meta.url)), "<TEST_DIR>")
|
|
19
|
+
.replace(/\/[^\s"]+\/\.cache\/mops/g, "<CACHE>")
|
|
20
|
+
.replace(/\/[^\s"]+\/Library\/Caches\/mops/g, "<CACHE>")
|
|
21
|
+
.replace(/\/[^\s"[\]]+\/moc(?:-wrapper)?(?=\s|$)/g, "moc-wrapper")
|
|
22
|
+
.replace(/\/[^\s"[\]]+\.motoko\/bin\/moc/g, "moc-wrapper"));
|
|
23
|
+
};
|
|
24
|
+
export const cliSnapshot = async (args, options, exitCode) => {
|
|
25
|
+
const result = await cli(args, options);
|
|
26
|
+
expect({
|
|
27
|
+
command: result.command,
|
|
28
|
+
exitCode: result.exitCode,
|
|
29
|
+
timedOut: result.timedOut,
|
|
30
|
+
stdio: Boolean(result.stdout || result.stderr),
|
|
31
|
+
}).toEqual({
|
|
32
|
+
command: result.command,
|
|
33
|
+
exitCode,
|
|
34
|
+
timedOut: false,
|
|
35
|
+
stdio: true,
|
|
36
|
+
});
|
|
37
|
+
expect({
|
|
38
|
+
exitCode: result.exitCode,
|
|
39
|
+
stdout: normalizePaths(result.stdout),
|
|
40
|
+
stderr: normalizePaths(result.stderr),
|
|
41
|
+
}).toMatchSnapshot();
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, test } from "@jest/globals";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { cliSnapshot } from "./helpers";
|
|
4
|
+
describe("lint", () => {
|
|
5
|
+
test("ok", async () => {
|
|
6
|
+
const cwd = path.join(import.meta.dirname, "lint");
|
|
7
|
+
await cliSnapshot(["lint", "Ok", "--verbose"], { cwd }, 0);
|
|
8
|
+
});
|
|
9
|
+
test("error", async () => {
|
|
10
|
+
const cwd = path.join(import.meta.dirname, "lint");
|
|
11
|
+
await cliSnapshot(["lint", "--verbose"], { cwd }, 1);
|
|
12
|
+
await cliSnapshot(["lint", "NoBoolSwitch", "--verbose"], { cwd }, 1);
|
|
13
|
+
await cliSnapshot(["lint", "DoesNotExist"], { cwd }, 1);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { cli } from "./helpers";
|
|
4
|
+
describe("moc-args", () => {
|
|
5
|
+
test("prints moc args from [moc] config", async () => {
|
|
6
|
+
const cwd = path.join(import.meta.dirname, "check/moc-args");
|
|
7
|
+
const result = await cli(["moc-args"], { cwd });
|
|
8
|
+
expect(result.exitCode).toBe(0);
|
|
9
|
+
expect(result.stdout).toBe("-Werror");
|
|
10
|
+
});
|
|
11
|
+
test("prints nothing when no [moc] config", async () => {
|
|
12
|
+
const cwd = path.join(import.meta.dirname, "check/success");
|
|
13
|
+
const result = await cli(["moc-args"], { cwd });
|
|
14
|
+
expect(result.exitCode).toBe(0);
|
|
15
|
+
expect(result.stdout).toBe("");
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { cli } from "./helpers";
|
|
4
|
+
describe("toolchain", () => {
|
|
5
|
+
test("file URI", async () => {
|
|
6
|
+
const cwd = path.join(import.meta.dirname, "toolchain");
|
|
7
|
+
const result = await cli(["toolchain", "bin", "moc"], { cwd });
|
|
8
|
+
expect(result.exitCode).toBe(0);
|
|
9
|
+
expect(result.stdout.trim()).toBe("./mock");
|
|
10
|
+
});
|
|
11
|
+
});
|
package/dist/types.d.ts
CHANGED
|
@@ -19,11 +19,17 @@ export type Config = {
|
|
|
19
19
|
"dev-dependencies"?: Dependencies;
|
|
20
20
|
toolchain?: Toolchain;
|
|
21
21
|
requirements?: Requirements;
|
|
22
|
+
moc?: {
|
|
23
|
+
args?: string[];
|
|
24
|
+
};
|
|
22
25
|
canisters?: Record<string, string | CanisterConfig>;
|
|
23
26
|
build?: {
|
|
24
27
|
outputDir?: string;
|
|
25
28
|
args?: string[];
|
|
26
29
|
};
|
|
30
|
+
lint?: {
|
|
31
|
+
args?: string[];
|
|
32
|
+
};
|
|
27
33
|
};
|
|
28
34
|
export type CanisterConfig = {
|
|
29
35
|
main: string;
|
|
@@ -42,8 +48,9 @@ export type Toolchain = {
|
|
|
42
48
|
moc?: string;
|
|
43
49
|
wasmtime?: string;
|
|
44
50
|
"pocket-ic"?: string;
|
|
51
|
+
lintoko?: string;
|
|
45
52
|
};
|
|
46
|
-
export type Tool = "moc" | "wasmtime" | "pocket-ic";
|
|
53
|
+
export type Tool = "moc" | "wasmtime" | "pocket-ic" | "lintoko";
|
|
47
54
|
export type Requirements = {
|
|
48
55
|
moc?: string;
|
|
49
56
|
};
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import
|
|
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";
|
|
3
5
|
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const wasm = require(path.join(__dirname, "../../wasm/pkg/nodejs/wasm.js"));
|
|
9
|
+
|
|
4
10
|
setWasmBindings(wasm);
|
|
5
11
|
|
|
6
12
|
export * from "../../cli.js";
|
package/error.ts
CHANGED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { execa } from "execa";
|
|
4
|
+
import {
|
|
5
|
+
TextDocument,
|
|
6
|
+
type TextEdit,
|
|
7
|
+
} from "vscode-languageserver-textdocument";
|
|
8
|
+
|
|
9
|
+
interface MocSpan {
|
|
10
|
+
file: string;
|
|
11
|
+
line_start: number;
|
|
12
|
+
column_start: number;
|
|
13
|
+
line_end: number;
|
|
14
|
+
column_end: number;
|
|
15
|
+
is_primary: boolean;
|
|
16
|
+
label: string | null;
|
|
17
|
+
suggested_replacement: string | null;
|
|
18
|
+
suggestion_applicability: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MocDiagnostic {
|
|
22
|
+
message: string;
|
|
23
|
+
code: string;
|
|
24
|
+
level: string;
|
|
25
|
+
spans: MocSpan[];
|
|
26
|
+
notes: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function parseDiagnostics(stdout: string): MocDiagnostic[] {
|
|
30
|
+
return stdout
|
|
31
|
+
.split("\n")
|
|
32
|
+
.filter((l) => l.trim())
|
|
33
|
+
.map((l) => {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(l) as MocDiagnostic;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
.filter((d) => d !== null);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface DiagnosticFix {
|
|
44
|
+
code: string;
|
|
45
|
+
edits: TextEdit[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractDiagnosticFixes(
|
|
49
|
+
diagnostics: MocDiagnostic[],
|
|
50
|
+
): Map<string, DiagnosticFix[]> {
|
|
51
|
+
const result = new Map<string, DiagnosticFix[]>();
|
|
52
|
+
|
|
53
|
+
for (const diag of diagnostics) {
|
|
54
|
+
const editsByFile = new Map<string, TextEdit[]>();
|
|
55
|
+
|
|
56
|
+
for (const span of diag.spans) {
|
|
57
|
+
if (
|
|
58
|
+
span.suggestion_applicability === "MachineApplicable" &&
|
|
59
|
+
span.suggested_replacement !== null
|
|
60
|
+
) {
|
|
61
|
+
const file = resolve(span.file);
|
|
62
|
+
const edits = editsByFile.get(file) ?? [];
|
|
63
|
+
edits.push({
|
|
64
|
+
range: {
|
|
65
|
+
start: {
|
|
66
|
+
line: span.line_start - 1,
|
|
67
|
+
character: span.column_start - 1,
|
|
68
|
+
},
|
|
69
|
+
end: {
|
|
70
|
+
line: span.line_end - 1,
|
|
71
|
+
character: span.column_end - 1,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
newText: span.suggested_replacement,
|
|
75
|
+
});
|
|
76
|
+
editsByFile.set(file, edits);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const [file, edits] of editsByFile) {
|
|
81
|
+
const existing = result.get(file) ?? [];
|
|
82
|
+
existing.push({ code: diag.code, edits });
|
|
83
|
+
result.set(file, existing);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type Range = TextEdit["range"];
|
|
91
|
+
|
|
92
|
+
function normalizeRange(range: Range): Range {
|
|
93
|
+
const { start, end } = range;
|
|
94
|
+
if (
|
|
95
|
+
start.line > end.line ||
|
|
96
|
+
(start.line === end.line && start.character > end.character)
|
|
97
|
+
) {
|
|
98
|
+
return { start: end, end: start };
|
|
99
|
+
}
|
|
100
|
+
return range;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface OffsetEdit {
|
|
104
|
+
start: number;
|
|
105
|
+
end: number;
|
|
106
|
+
newText: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Applies diagnostic fixes to a document, processing each diagnostic as
|
|
111
|
+
* an atomic unit. If any edit from a diagnostic overlaps with an already-accepted
|
|
112
|
+
* edit, the entire diagnostic is skipped (picked up in subsequent iterations).
|
|
113
|
+
* Based on vscode-languageserver-textdocument's TextDocument.applyEdits.
|
|
114
|
+
*/
|
|
115
|
+
function applyDiagnosticFixes(
|
|
116
|
+
doc: TextDocument,
|
|
117
|
+
fixes: DiagnosticFix[],
|
|
118
|
+
): { text: string; appliedCodes: string[] } {
|
|
119
|
+
const acceptedEdits: OffsetEdit[] = [];
|
|
120
|
+
const appliedCodes: string[] = [];
|
|
121
|
+
|
|
122
|
+
for (const fix of fixes) {
|
|
123
|
+
const offsets: OffsetEdit[] = fix.edits.map((e) => {
|
|
124
|
+
const range = normalizeRange(e.range);
|
|
125
|
+
return {
|
|
126
|
+
start: doc.offsetAt(range.start),
|
|
127
|
+
end: doc.offsetAt(range.end),
|
|
128
|
+
newText: e.newText,
|
|
129
|
+
};
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const overlaps = offsets.some((o) =>
|
|
133
|
+
acceptedEdits.some((a) => o.start < a.end && o.end > a.start),
|
|
134
|
+
);
|
|
135
|
+
if (overlaps) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
acceptedEdits.push(...offsets);
|
|
140
|
+
appliedCodes.push(fix.code);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
acceptedEdits.sort((a, b) => a.start - b.start);
|
|
144
|
+
|
|
145
|
+
const text = doc.getText();
|
|
146
|
+
const spans: string[] = [];
|
|
147
|
+
let lastOffset = 0;
|
|
148
|
+
|
|
149
|
+
for (const edit of acceptedEdits) {
|
|
150
|
+
if (edit.start < lastOffset) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (edit.start > lastOffset) {
|
|
154
|
+
spans.push(text.substring(lastOffset, edit.start));
|
|
155
|
+
}
|
|
156
|
+
if (edit.newText.length) {
|
|
157
|
+
spans.push(edit.newText);
|
|
158
|
+
}
|
|
159
|
+
lastOffset = edit.end;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
spans.push(text.substring(lastOffset));
|
|
163
|
+
return { text: spans.join(""), appliedCodes };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const MAX_FIX_ITERATIONS = 10;
|
|
167
|
+
|
|
168
|
+
export interface AutofixResult {
|
|
169
|
+
/** Map of file path → diagnostic codes fixed in that file */
|
|
170
|
+
fixedFiles: Map<string, string[]>;
|
|
171
|
+
totalFixCount: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export async function autofixMotoko(
|
|
175
|
+
mocPath: string,
|
|
176
|
+
files: string[],
|
|
177
|
+
mocArgs: string[],
|
|
178
|
+
): Promise<AutofixResult | null> {
|
|
179
|
+
const fixedFilesCodes = new Map<string, string[]>();
|
|
180
|
+
|
|
181
|
+
for (let iteration = 0; iteration < MAX_FIX_ITERATIONS; iteration++) {
|
|
182
|
+
const fixesByFile = new Map<string, DiagnosticFix[]>();
|
|
183
|
+
|
|
184
|
+
for (const file of files) {
|
|
185
|
+
const result = await execa(
|
|
186
|
+
mocPath,
|
|
187
|
+
[file, ...mocArgs, "--error-format=json"],
|
|
188
|
+
{ stdio: "pipe", reject: false },
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const diagnostics = parseDiagnostics(result.stdout);
|
|
192
|
+
for (const [targetFile, fixes] of extractDiagnosticFixes(diagnostics)) {
|
|
193
|
+
const existing = fixesByFile.get(targetFile) ?? [];
|
|
194
|
+
existing.push(...fixes);
|
|
195
|
+
fixesByFile.set(targetFile, existing);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (fixesByFile.size === 0) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let progress = false;
|
|
204
|
+
|
|
205
|
+
for (const [file, fixes] of fixesByFile) {
|
|
206
|
+
const original = await readFile(file, "utf-8");
|
|
207
|
+
const doc = TextDocument.create(`file://${file}`, "motoko", 0, original);
|
|
208
|
+
const { text: result, appliedCodes } = applyDiagnosticFixes(doc, fixes);
|
|
209
|
+
|
|
210
|
+
if (result === original) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
await writeFile(file, result, "utf-8");
|
|
215
|
+
progress = true;
|
|
216
|
+
|
|
217
|
+
const existing = fixedFilesCodes.get(file) ?? [];
|
|
218
|
+
existing.push(...appliedCodes);
|
|
219
|
+
fixedFilesCodes.set(file, existing);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!progress) {
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (fixedFilesCodes.size === 0) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
let totalFixCount = 0;
|
|
232
|
+
for (const codes of fixedFilesCodes.values()) {
|
|
233
|
+
totalFixCount += codes.length;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
fixedFiles: fixedFilesCodes,
|
|
238
|
+
totalFixCount,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { type SemVer, parse } from "semver";
|
|
3
|
+
import { readConfig } from "../mops.js";
|
|
2
4
|
import { getMocPath } from "./get-moc-path.js";
|
|
3
5
|
|
|
6
|
+
export function getMocSemVer(): SemVer | null {
|
|
7
|
+
return parse(getMocVersion(false));
|
|
8
|
+
}
|
|
9
|
+
|
|
4
10
|
export function getMocVersion(throwOnError = false): string {
|
|
5
|
-
let
|
|
11
|
+
let configVersion = readConfig().toolchain?.moc;
|
|
12
|
+
if (configVersion) {
|
|
13
|
+
return configVersion;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const mocPath = getMocPath(false);
|
|
6
17
|
if (!mocPath) {
|
|
7
18
|
return "";
|
|
8
19
|
}
|