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
@@ -0,0 +1,7 @@
1
+ persistent actor {
2
+ public func hello() : async Text {
3
+ "Hello, World!";
4
+ };
5
+ };
6
+
7
+ thisshouldnotcompile;
@@ -0,0 +1,2 @@
1
+ [dependencies]
2
+ core = "2.0.0"
@@ -0,0 +1,11 @@
1
+ // M0223: Redundant type instantiation
2
+ // The type annotation is not needed when it can be inferred
3
+
4
+ persistent actor {
5
+ public func testM0223() : async () {
6
+ func identity<T>(x : T) : T = x;
7
+ let varArray : [var Nat] = [var 1];
8
+ let nat = identity<Nat>(1);
9
+ varArray[0] := nat;
10
+ };
11
+ };
@@ -0,0 +1,11 @@
1
+ // M0236: Suggested to use dot notation
2
+ // Function calls can be rewritten using dot notation
3
+ import List "mo:core/List";
4
+ import Nat "mo:core/Nat";
5
+
6
+ persistent actor {
7
+ public func testM0236() : async () {
8
+ let list = List.fromArray<Nat>([1, 2, 3]);
9
+ List.sortInPlace(list);
10
+ };
11
+ };
@@ -0,0 +1,11 @@
1
+ // M0237: Redundant explicit implicit argument
2
+ // Some arguments can be inferred and don't need to be specified
3
+ import List "mo:core/List";
4
+ import Nat "mo:core/Nat";
5
+
6
+ persistent actor {
7
+ public func testM0237() : async () {
8
+ let list = List.fromArray<Nat>([3, 2, 1]);
9
+ list.sortInPlace(Nat.compare);
10
+ };
11
+ };
@@ -0,0 +1,7 @@
1
+ // Clean file — no fixable warnings
2
+ persistent actor {
3
+ public func example() : async () {
4
+ let _x : ?Text = null;
5
+ ();
6
+ };
7
+ };
@@ -0,0 +1,143 @@
1
+ import Map "mo:core/Map";
2
+ import Nat "mo:core/Nat";
3
+ import Text "mo:core/Text";
4
+ import { type Order } "mo:core/Order";
5
+
6
+ // --- M0223: redundant type instantiation ---
7
+
8
+ do {
9
+ func inferred<T>(x : T) : T = x;
10
+ let n1 = inferred<Nat>(1);
11
+ ignore n1;
12
+ };
13
+
14
+ // --- M0236: contextual dot notation ---
15
+
16
+ do {
17
+ let m = Map.empty<Nat, Text>();
18
+ // single arg
19
+ ignore Map.size(m); // warn M0236
20
+
21
+ // multi arg with implicit -> M0236 + M0237
22
+ ignore Map.get(m, Nat.compare, 1); // warn M0236 + M0237
23
+
24
+ // complex receiver
25
+ ignore Map.size(
26
+ Map.empty<Nat, Text>()
27
+ ); // warn M0236
28
+
29
+ // multiline call -> M0236 + M0237
30
+ Map.add(
31
+ m,
32
+ Nat.compare,
33
+ 1,
34
+ "John",
35
+ ); // warn M0236 + M0237
36
+ };
37
+
38
+ // --- M0237: implicit argument removal ---
39
+
40
+ do {
41
+ let m = Map.empty<Nat, Text>();
42
+
43
+ // single line
44
+ ignore m.get(Nat.compare, 1); // warn M0237
45
+
46
+ // multiline
47
+ ignore m.get(
48
+ Nat.compare,
49
+ 1,
50
+ ); // warn M0237
51
+ };
52
+
53
+ // --- M0237: complex implicit patterns ---
54
+
55
+ module Impl {
56
+ // implicit in the middle: f(self, implicit, key)
57
+ public func get<K, V>(
58
+ self : [(K, V)],
59
+ _cmp : (implicit : (compare : (K, K) -> Order)),
60
+ key : K,
61
+ ) : ?V { ignore self; ignore key; null };
62
+
63
+ // two adjacent implicits: f(self, implicit1, implicit2, key, value)
64
+ public func put<K, V>(
65
+ self : [(K, V)],
66
+ _cmpK : (implicit : (compare : (K, K) -> Order)),
67
+ _cmpV : (implicit : (compare : (V, V) -> Order)),
68
+ key : K,
69
+ value : V,
70
+ ) : [(K, V)] { ignore key; ignore value; self };
71
+
72
+ // implicit at the end
73
+ public func find<K, V>(
74
+ self : [(K, V)],
75
+ key : K,
76
+ _cmp : (implicit : (compare : (K, K) -> Order)),
77
+ ) : ?V { ignore self; ignore key; null };
78
+ public func sort1<K, V>(
79
+ self : [(K, V)],
80
+ _cmp : (implicit : (compare : (K, K) -> Order)),
81
+ ) : [(K, V)] { self };
82
+ public func sort2<K, V>(
83
+ notSelf : [(K, V)],
84
+ _cmp : (implicit : (compare : (K, K) -> Order)),
85
+ ) : [(K, V)] { notSelf };
86
+
87
+ // all implicits: f(implicit1, implicit2)
88
+ public func make<K, V>(
89
+ _cmpK : (implicit : (compare : (K, K) -> Order)),
90
+ _cmpV : (implicit : (compare : (V, V) -> Order)),
91
+ ) : [(K, V)] { [] };
92
+
93
+ // non-adjacent implicits: f(self, implicit1, key, implicit2, value)
94
+ public func update<K, V>(
95
+ self : [(K, V)],
96
+ _cmpK : (implicit : (compare : (K, K) -> Order)),
97
+ key : K,
98
+ _cmpV : (implicit : (compare : (V, V) -> Order)),
99
+ value : V,
100
+ ) : [(K, V)] { ignore key; ignore value; self };
101
+ };
102
+
103
+ do {
104
+ let data : [(Nat, Text)] = [];
105
+
106
+ // implicit in the middle -> M0236 + M0237
107
+ ignore Impl.get(data, Nat.compare, 1);
108
+
109
+ // two adjacent implicits -> M0236 + M0237 x2
110
+ ignore Impl.put(data, Nat.compare, Text.compare, 1, "a");
111
+
112
+ // implicit at the end -> M0236 + M0237
113
+ ignore Impl.find(data, 1, Nat.compare);
114
+ ignore Impl.sort1(data, Nat.compare); // -> M0236 + M0237
115
+ ignore Impl.sort2(data, Nat.compare); // no dot suggestion (notSelf), M0237 only
116
+
117
+ // all implicits -> M0237 x2
118
+ let _ = Impl.make<Nat, Text>(Nat.compare, Text.compare);
119
+
120
+ // non-adjacent implicits -> M0236 + M0237 x2
121
+ ignore Impl.update(data, Nat.compare, 1, Text.compare, "a");
122
+
123
+ // multiline: two adjacent implicits -> M0236 + M0237 x2
124
+ ignore Impl.put(
125
+ data,
126
+ Nat.compare,
127
+ Text.compare,
128
+ 1,
129
+ "a",
130
+ );
131
+ };
132
+
133
+ // --- Mix: M0223 + M0236 + M0237 ---
134
+
135
+ do {
136
+ // NB: Must use `let _ = ...` to get the 'redundant type instantiation' error
137
+ let _ = Map.insert<Nat, Text>(
138
+ Map.empty<Nat, Text>(),
139
+ Nat.compare,
140
+ 1,
141
+ "John",
142
+ ); // warn M0223 + M0236 + M0237
143
+ };
@@ -0,0 +1,5 @@
1
+ [dependencies]
2
+ core = "2.0.0"
3
+
4
+ [toolchain]
5
+ moc = "1.3.0"
@@ -0,0 +1,10 @@
1
+ import Array "mo:core/Array";
2
+
3
+ // Overlapping fixable errors (nested calls produce overlapping M0223 + M0236 edits)
4
+ do {
5
+ let ar = [1, 2, 3];
6
+ let _ = Array.filter<Nat>(
7
+ Array.filter<Nat>(ar, func(x) { x > 0 }),
8
+ func(x) { x > 0 },
9
+ );
10
+ };
@@ -0,0 +1,9 @@
1
+ import List "mo:core/List";
2
+ import Nat "mo:core/Nat";
3
+
4
+ module {
5
+ public func test() {
6
+ let list = List.fromArray<Nat>([3, 2, 1]);
7
+ List.sortInPlace(list);
8
+ };
9
+ };
@@ -0,0 +1,9 @@
1
+ import Lib "./transitive-lib";
2
+
3
+ persistent actor {
4
+ public func run() : async () {
5
+ func identity<T>(x : T) : T = x;
6
+ let _ = identity<Nat>(1);
7
+ Lib.test();
8
+ };
9
+ };
@@ -0,0 +1,5 @@
1
+ persistent actor {
2
+ public func example() : async () {
3
+ let unused = 123;
4
+ };
5
+ };
@@ -0,0 +1,2 @@
1
+ [moc]
2
+ args = ["-Werror"]
@@ -0,0 +1,5 @@
1
+ persistent actor {
2
+ public func hello() : async Text {
3
+ "Hello, World!";
4
+ };
5
+ };
@@ -0,0 +1,5 @@
1
+ persistent actor {
2
+ public func example() : async () {
3
+ let unused = 123;
4
+ };
5
+ };
@@ -0,0 +1,2 @@
1
+ [dependencies]
2
+ core = "2.0.0"
@@ -0,0 +1,22 @@
1
+ import { describe, test } from "@jest/globals";
2
+ import path from "path";
3
+ import { cliSnapshot } from "./helpers";
4
+
5
+ describe("check-candid", () => {
6
+ test("ok", async () => {
7
+ const cwd = path.join(import.meta.dirname, "check-candid");
8
+ await cliSnapshot(["check-candid", "a.did", "a.did"], { cwd }, 0);
9
+ await cliSnapshot(["check-candid", "b.did", "b.did"], { cwd }, 0);
10
+ await cliSnapshot(["check-candid", "c.did", "c.did"], { cwd }, 0);
11
+ await cliSnapshot(["check-candid", "a.did", "b.did"], { cwd }, 0);
12
+ await cliSnapshot(["check-candid", "b.did", "a.did"], { cwd }, 0);
13
+ });
14
+
15
+ test("error", async () => {
16
+ const cwd = path.join(import.meta.dirname, "check-candid");
17
+ await cliSnapshot(["check-candid", "a.did", "c.did"], { cwd }, 1);
18
+ await cliSnapshot(["check-candid", "c.did", "a.did"], { cwd }, 1);
19
+ await cliSnapshot(["check-candid", "b.did", "c.did"], { cwd }, 1);
20
+ await cliSnapshot(["check-candid", "c.did", "b.did"], { cwd }, 1);
21
+ });
22
+ });
@@ -0,0 +1,134 @@
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
+
7
+ function countCodes(stdout: string): Record<string, number> {
8
+ const counts: Record<string, number> = {};
9
+ for (const diag of parseDiagnostics(stdout)) {
10
+ counts[diag.code] = (counts[diag.code] ?? 0) + 1;
11
+ }
12
+ return counts;
13
+ }
14
+
15
+ describe("check --fix", () => {
16
+ const fixDir = path.join(import.meta.dirname, "check/fix");
17
+ const runDir = path.join(fixDir, "run");
18
+ const warningFlags = "-W=M0223,M0236,M0237";
19
+ const diagnosticFlags = [warningFlags, "--error-format=json"];
20
+
21
+ beforeAll(() => {
22
+ for (const file of readdirSync(runDir).filter((f) => f.endsWith(".mo"))) {
23
+ unlinkSync(path.join(runDir, file));
24
+ }
25
+ });
26
+
27
+ function copyFixture(file: string): string {
28
+ const dest = path.join(runDir, file);
29
+ cpSync(path.join(fixDir, file), dest);
30
+ return dest;
31
+ }
32
+
33
+ async function testCheckFix(
34
+ file: string,
35
+ expectedDiagnostics: Record<string, number>,
36
+ expectedAfterDiagnostics: Record<string, number> = {},
37
+ ): Promise<string> {
38
+ const runFilePath = copyFixture(file);
39
+
40
+ const beforeResult = await cli(
41
+ ["check", runFilePath, "--", ...diagnosticFlags],
42
+ { cwd: fixDir },
43
+ );
44
+ expect(countCodes(beforeResult.stdout)).toEqual(expectedDiagnostics);
45
+
46
+ const fixResult = await cli(
47
+ ["check", runFilePath, "--fix", "--", warningFlags],
48
+ { cwd: fixDir },
49
+ );
50
+
51
+ expect(normalizePaths(fixResult.stdout)).toMatchSnapshot("fix output");
52
+ expect(readFileSync(runFilePath, "utf-8")).toMatchSnapshot();
53
+
54
+ const afterResult = await cli(
55
+ ["check", runFilePath, "--", ...diagnosticFlags],
56
+ { cwd: fixDir },
57
+ );
58
+ expect(countCodes(afterResult.stdout)).toEqual(expectedAfterDiagnostics);
59
+
60
+ return runFilePath;
61
+ }
62
+
63
+ test("M0223", async () => {
64
+ await testCheckFix("M0223.mo", { M0223: 1 });
65
+ });
66
+
67
+ test("M0236", async () => {
68
+ await testCheckFix("M0236.mo", { M0236: 1 });
69
+ });
70
+
71
+ test("M0237", async () => {
72
+ await testCheckFix("M0237.mo", { M0237: 1 });
73
+ });
74
+
75
+ test("edit-suggestions", async () => {
76
+ await testCheckFix("edit-suggestions.mo", {
77
+ M0223: 2,
78
+ M0236: 11,
79
+ M0237: 17,
80
+ });
81
+ });
82
+
83
+ test("overlapping edits", async () => {
84
+ await testCheckFix("overlapping.mo", { M0223: 1, M0236: 2 });
85
+ });
86
+
87
+ test("transitive imports", async () => {
88
+ const runMainPath = copyFixture("transitive-main.mo");
89
+ const runLibPath = copyFixture("transitive-lib.mo");
90
+
91
+ const fixResult = await cli(
92
+ ["check", runMainPath, "--fix", "--", warningFlags],
93
+ { cwd: fixDir },
94
+ );
95
+
96
+ expect(normalizePaths(fixResult.stdout)).toMatchSnapshot("fix output");
97
+ expect(readFileSync(runMainPath, "utf-8")).toMatchSnapshot("main file");
98
+ expect(readFileSync(runLibPath, "utf-8")).toMatchSnapshot("lib file");
99
+
100
+ const afterResult = await cli(
101
+ ["check", runMainPath, "--", ...diagnosticFlags],
102
+ { cwd: fixDir },
103
+ );
104
+ expect(countCodes(afterResult.stdout)).toEqual({});
105
+ });
106
+
107
+ test("--error-format=human does not break --fix", async () => {
108
+ const runFilePath = copyFixture("M0223.mo");
109
+
110
+ const fixResult = await cli(
111
+ [
112
+ "check",
113
+ runFilePath,
114
+ "--fix",
115
+ "--",
116
+ warningFlags,
117
+ "--error-format=human",
118
+ ],
119
+ { cwd: fixDir },
120
+ );
121
+
122
+ expect(fixResult.stdout).toContain("1 fix applied");
123
+ expect(readFileSync(runFilePath, "utf-8")).not.toContain("<Nat>");
124
+ });
125
+
126
+ test("verbose", async () => {
127
+ const result = await cli(["check", "Ok.mo", "--fix", "--verbose"], {
128
+ cwd: fixDir,
129
+ });
130
+ expect(result.exitCode).toBe(0);
131
+ expect(result.stdout).toContain("Attempting to fix files");
132
+ expect(result.stdout).toContain("No fixes were needed");
133
+ });
134
+ });
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import path from "path";
3
+ import { cliSnapshot } from "./helpers";
4
+
5
+ describe("check", () => {
6
+ test("ok", async () => {
7
+ const cwd = path.join(import.meta.dirname, "check/success");
8
+ await cliSnapshot(["check", "Ok.mo"], { cwd }, 0);
9
+ await cliSnapshot(["check", "Ok.mo", "--verbose"], { cwd }, 0);
10
+ });
11
+
12
+ test("error", async () => {
13
+ const cwd = path.join(import.meta.dirname, "check/error");
14
+ await cliSnapshot(["check", "Error.mo"], { cwd }, 1);
15
+ await cliSnapshot(["check", "Ok.mo", "Error.mo"], { cwd }, 1);
16
+ });
17
+
18
+ test("warning", async () => {
19
+ const cwd = path.join(import.meta.dirname, "check/success");
20
+ const result = await cliSnapshot(["check", "Warning.mo"], { cwd }, 0);
21
+ expect(result.stderr).toMatch(/warning \[M0194\]/);
22
+ expect(result.stderr).toMatch(/unused identifier/);
23
+ });
24
+
25
+ test("warning verbose", async () => {
26
+ const cwd = path.join(import.meta.dirname, "check/success");
27
+ const result = await cliSnapshot(
28
+ ["check", "Warning.mo", "--verbose"],
29
+ { cwd },
30
+ 0,
31
+ );
32
+ expect(result.stderr).toMatch(/warning \[M0194\]/);
33
+ expect(result.stderr).toMatch(/unused identifier/);
34
+ });
35
+
36
+ test("warning with -Werror flag", async () => {
37
+ const cwd = path.join(import.meta.dirname, "check/success");
38
+ const result = await cliSnapshot(
39
+ ["check", "Warning.mo", "--", "-Werror"],
40
+ { cwd },
41
+ 1,
42
+ );
43
+ expect(result.stderr).toMatch(/warning \[M0194\]/);
44
+ expect(result.stderr).toMatch(/unused identifier/);
45
+ });
46
+
47
+ test("[moc] args are passed to moc", async () => {
48
+ const cwd = path.join(import.meta.dirname, "check/moc-args");
49
+ await cliSnapshot(["check", "Warning.mo"], { cwd }, 1);
50
+ });
51
+ });
package/tests/cli.test.ts CHANGED
@@ -1,82 +1,12 @@
1
1
  import { describe, expect, test } from "@jest/globals";
2
- import { execa } from "execa";
3
- import path from "path";
2
+ import { cli } from "./helpers";
4
3
 
5
- interface CliOptions {
6
- cwd?: string;
7
- }
8
-
9
- const cli = async (args: string[], { cwd }: CliOptions = {}) => {
10
- return await execa("npm", ["run", "mops", "--", ...args], {
11
- env: { MOPS_CWD: cwd },
12
- stdio: "pipe",
13
- reject: false,
14
- });
15
- };
16
-
17
- const cliSnapshot = async (
18
- args: string[],
19
- options: CliOptions,
20
- exitCode: number,
21
- ) => {
22
- const result = await cli(args, options);
23
- expect({
24
- command: result.command,
25
- exitCode: result.exitCode,
26
- timedOut: result.timedOut,
27
- stdio: Boolean(result.stdout || result.stderr),
28
- }).toEqual({
29
- command: result.command,
30
- exitCode,
31
- timedOut: false,
32
- stdio: true,
33
- });
34
- expect({
35
- exitCode: result.exitCode,
36
- stdout: result.stdout,
37
- stderr: result.stderr,
38
- }).toMatchSnapshot();
39
- return result;
40
- };
41
-
42
- describe("mops", () => {
43
- test("version", async () => {
4
+ describe("cli", () => {
5
+ test("--version", async () => {
44
6
  expect((await cli(["--version"])).stdout).toMatch(/CLI \d+\.\d+\.\d+/);
45
7
  });
46
8
 
47
- test("help", async () => {
9
+ test("--help", async () => {
48
10
  expect((await cli(["--help"])).stdout).toMatch(/^Usage: mops/m);
49
11
  });
50
-
51
- test("build success", async () => {
52
- const cwd = path.join(import.meta.dirname, "build/success");
53
- await cliSnapshot(["build"], { cwd }, 0);
54
- await cliSnapshot(["build", "foo"], { cwd }, 0);
55
- await cliSnapshot(["build", "bar"], { cwd }, 0);
56
- await cliSnapshot(["build", "foo", "bar"], { cwd }, 0);
57
- });
58
-
59
- test("build error", async () => {
60
- const cwd = path.join(import.meta.dirname, "build/error");
61
- await cliSnapshot(["build", "foo"], { cwd }, 0);
62
- expect((await cliSnapshot(["build", "bar"], { cwd }, 1)).stderr).toMatch(
63
- "Candid compatibility check failed for canister bar",
64
- );
65
- expect(
66
- (await cliSnapshot(["build", "foo", "bar"], { cwd }, 1)).stderr,
67
- ).toMatch("Candid compatibility check failed for canister bar");
68
- });
69
-
70
- test("check-candid", async () => {
71
- const cwd = path.join(import.meta.dirname, "check-candid");
72
- await cliSnapshot(["check-candid", "a.did", "a.did"], { cwd }, 0);
73
- await cliSnapshot(["check-candid", "b.did", "b.did"], { cwd }, 0);
74
- await cliSnapshot(["check-candid", "c.did", "c.did"], { cwd }, 0);
75
- await cliSnapshot(["check-candid", "a.did", "b.did"], { cwd }, 0);
76
- await cliSnapshot(["check-candid", "b.did", "a.did"], { cwd }, 0);
77
- await cliSnapshot(["check-candid", "a.did", "c.did"], { cwd }, 1);
78
- await cliSnapshot(["check-candid", "c.did", "a.did"], { cwd }, 1);
79
- await cliSnapshot(["check-candid", "b.did", "c.did"], { cwd }, 1);
80
- await cliSnapshot(["check-candid", "c.did", "b.did"], { cwd }, 1);
81
- });
82
12
  });
@@ -0,0 +1,58 @@
1
+ import { expect } from "@jest/globals";
2
+ import { execa } from "execa";
3
+ import { dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+
6
+ export interface CliOptions {
7
+ cwd?: string;
8
+ }
9
+
10
+ export const cli = async (args: string[], { cwd }: CliOptions = {}) => {
11
+ return await execa("npm", ["run", "--silent", "mops", "--", ...args], {
12
+ env: { ...process.env, ...(cwd != null && { MOPS_CWD: cwd }) },
13
+ ...(cwd != null && { cwd }),
14
+ stdio: "pipe",
15
+ reject: false,
16
+ });
17
+ };
18
+
19
+ // Strip ANSI escape codes for portable snapshots (avoid control char in regex literal)
20
+ const stripAnsi = (s: string) =>
21
+ s.replace(new RegExp(`\u001b\\[[0-9;]*m`, "g"), "");
22
+
23
+ export const normalizePaths = (text: string): string => {
24
+ // Replace absolute paths with placeholders for CI
25
+ return stripAnsi(
26
+ text
27
+ .replaceAll(dirname(fileURLToPath(import.meta.url)), "<TEST_DIR>")
28
+ .replace(/\/[^\s"]+\/\.cache\/mops/g, "<CACHE>")
29
+ .replace(/\/[^\s"]+\/Library\/Caches\/mops/g, "<CACHE>")
30
+ .replace(/\/[^\s"[\]]+\/moc(?:-wrapper)?(?=\s|$)/g, "moc-wrapper")
31
+ .replace(/\/[^\s"[\]]+\.motoko\/bin\/moc/g, "moc-wrapper"),
32
+ );
33
+ };
34
+
35
+ export const cliSnapshot = async (
36
+ args: string[],
37
+ options: CliOptions,
38
+ exitCode: number,
39
+ ) => {
40
+ const result = await cli(args, options);
41
+ expect({
42
+ command: result.command,
43
+ exitCode: result.exitCode,
44
+ timedOut: result.timedOut,
45
+ stdio: Boolean(result.stdout || result.stderr),
46
+ }).toEqual({
47
+ command: result.command,
48
+ exitCode,
49
+ timedOut: false,
50
+ stdio: true,
51
+ });
52
+ expect({
53
+ exitCode: result.exitCode,
54
+ stdout: normalizePaths(result.stdout),
55
+ stderr: normalizePaths(result.stderr),
56
+ }).toMatchSnapshot();
57
+ return result;
58
+ };
@@ -0,0 +1,9 @@
1
+ name = "no-bool-switch"
2
+ description = "Don't switch on boolean values, use if instead"
3
+ query = """
4
+ (switch_exp
5
+ (case [
6
+ (lit_pat (bool_literal))
7
+ (tup_pat . (lit_pat (bool_literal)) @trailing)
8
+ ])) @error
9
+ """
@@ -0,0 +1,4 @@
1
+ [dependencies]
2
+
3
+ [toolchain]
4
+ lintoko = "0.7.0"
@@ -0,0 +1,8 @@
1
+ module {
2
+ public func boolSwitch(b : Bool) : Bool {
3
+ switch (b) {
4
+ case false { false };
5
+ case true { true };
6
+ };
7
+ };
8
+ };
@@ -0,0 +1,5 @@
1
+ module {
2
+ public func greet(name : Text) : Text {
3
+ "Hello, " # name # "!";
4
+ };
5
+ };
@@ -0,0 +1,17 @@
1
+ import { describe, test } from "@jest/globals";
2
+ import path from "path";
3
+ import { cliSnapshot } from "./helpers";
4
+
5
+ describe("lint", () => {
6
+ test("ok", async () => {
7
+ const cwd = path.join(import.meta.dirname, "lint");
8
+ await cliSnapshot(["lint", "Ok", "--verbose"], { cwd }, 0);
9
+ });
10
+
11
+ test("error", async () => {
12
+ const cwd = path.join(import.meta.dirname, "lint");
13
+ await cliSnapshot(["lint", "--verbose"], { cwd }, 1);
14
+ await cliSnapshot(["lint", "NoBoolSwitch", "--verbose"], { cwd }, 1);
15
+ await cliSnapshot(["lint", "DoesNotExist"], { cwd }, 1);
16
+ });
17
+ });