redscript-mc 3.0.1 → 3.0.2

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 (225) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/README.md +119 -313
  3. package/README.zh.md +118 -314
  4. package/ROADMAP.md +5 -5
  5. package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
  6. package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
  7. package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
  8. package/dist/data/impl_test/function/load.mcfunction +1 -0
  9. package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
  10. package/dist/data/minecraft/tags/function/load.json +5 -0
  11. package/dist/data/playground/function/load.mcfunction +1 -0
  12. package/dist/data/playground/function/start.mcfunction +4 -0
  13. package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
  14. package/dist/data/playground/function/stop.mcfunction +5 -0
  15. package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
  16. package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
  17. package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
  18. package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
  19. package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
  20. package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
  21. package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
  22. package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
  23. package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
  24. package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
  25. package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
  26. package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
  27. package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
  28. package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
  29. package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
  30. package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
  31. package/dist/data/test/function/load.mcfunction +1 -0
  32. package/dist/data/test/function/say_at.mcfunction +6 -0
  33. package/dist/data/test/function/test.mcfunction +4 -0
  34. package/dist/pack.mcmeta +6 -0
  35. package/dist/package.json +1 -1
  36. package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
  37. package/dist/src/__tests__/formatter-extra.test.js +123 -0
  38. package/dist/src/__tests__/global-vars.test.d.ts +13 -0
  39. package/dist/src/__tests__/global-vars.test.js +156 -0
  40. package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
  41. package/dist/src/__tests__/lint/new-rules.test.js +402 -0
  42. package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
  43. package/dist/src/__tests__/lsp-rename.test.js +157 -0
  44. package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
  45. package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
  46. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
  47. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
  48. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
  49. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
  50. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
  51. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
  52. package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
  53. package/dist/src/__tests__/mc-syntax.test.js +4 -1
  54. package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
  55. package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
  56. package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
  57. package/dist/src/__tests__/optimizer-cse.test.js +226 -0
  58. package/dist/src/__tests__/parser.test.js +4 -13
  59. package/dist/src/__tests__/repl-server-extra.test.js +6 -7
  60. package/dist/src/__tests__/repl-server.test.js +5 -7
  61. package/dist/src/__tests__/stdlib/queue.test.js +6 -6
  62. package/dist/src/cli.js +0 -0
  63. package/dist/src/lexer/index.js +2 -1
  64. package/dist/src/lint/index.d.ts +12 -5
  65. package/dist/src/lint/index.js +730 -5
  66. package/dist/src/lsp/main.js +0 -0
  67. package/dist/src/mc-test/client.d.ts +21 -0
  68. package/dist/src/mc-test/client.js +34 -0
  69. package/dist/src/mir/lower.js +108 -6
  70. package/dist/src/optimizer/interprocedural.js +37 -2
  71. package/dist/src/parser/decl-parser.d.ts +19 -0
  72. package/dist/src/parser/decl-parser.js +323 -0
  73. package/dist/src/parser/expr-parser.d.ts +46 -0
  74. package/dist/src/parser/expr-parser.js +759 -0
  75. package/dist/src/parser/index.d.ts +8 -129
  76. package/dist/src/parser/index.js +13 -2262
  77. package/dist/src/parser/stmt-parser.d.ts +28 -0
  78. package/dist/src/parser/stmt-parser.js +577 -0
  79. package/dist/src/parser/type-parser.d.ts +20 -0
  80. package/dist/src/parser/type-parser.js +257 -0
  81. package/dist/src/parser/utils.d.ts +34 -0
  82. package/dist/src/parser/utils.js +141 -0
  83. package/docs/dev/README-mc-integration-tests.md +141 -0
  84. package/docs/lint-rules.md +162 -0
  85. package/docs/stdlib/bigint.md +2 -0
  86. package/editors/vscode/README.md +63 -41
  87. package/editors/vscode/out/extension.js +1881 -1776
  88. package/editors/vscode/out/lsp-server.js +4257 -3651
  89. package/editors/vscode/package-lock.json +3 -3
  90. package/editors/vscode/package.json +1 -1
  91. package/examples/loops-demo.mcrs +87 -0
  92. package/package.json +1 -1
  93. package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
  94. package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
  95. package/redscript-docs/docs/en/stdlib/bits.md +292 -0
  96. package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
  97. package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
  98. package/redscript-docs/docs/en/stdlib/color.md +353 -0
  99. package/redscript-docs/docs/en/stdlib/combat.md +88 -0
  100. package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
  101. package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
  102. package/redscript-docs/docs/en/stdlib/easing.md +558 -0
  103. package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
  104. package/redscript-docs/docs/en/stdlib/effects.md +324 -0
  105. package/redscript-docs/docs/en/stdlib/events.md +3 -0
  106. package/redscript-docs/docs/en/stdlib/expr.md +45 -0
  107. package/redscript-docs/docs/en/stdlib/fft.md +141 -0
  108. package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
  109. package/redscript-docs/docs/en/stdlib/graph.md +259 -0
  110. package/redscript-docs/docs/en/stdlib/heap.md +185 -0
  111. package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
  112. package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
  113. package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
  114. package/redscript-docs/docs/en/stdlib/list.md +559 -0
  115. package/redscript-docs/docs/en/stdlib/map.md +140 -0
  116. package/redscript-docs/docs/en/stdlib/math.md +193 -0
  117. package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
  118. package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
  119. package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
  120. package/redscript-docs/docs/en/stdlib/noise.md +244 -0
  121. package/redscript-docs/docs/en/stdlib/ode.md +253 -0
  122. package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
  123. package/redscript-docs/docs/en/stdlib/particles.md +311 -0
  124. package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
  125. package/redscript-docs/docs/en/stdlib/physics.md +493 -0
  126. package/redscript-docs/docs/en/stdlib/player.md +78 -0
  127. package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
  128. package/redscript-docs/docs/en/stdlib/queue.md +134 -0
  129. package/redscript-docs/docs/en/stdlib/random.md +223 -0
  130. package/redscript-docs/docs/en/stdlib/result.md +143 -0
  131. package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
  132. package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
  133. package/redscript-docs/docs/en/stdlib/sets.md +101 -0
  134. package/redscript-docs/docs/en/stdlib/signal.md +400 -0
  135. package/redscript-docs/docs/en/stdlib/sort.md +104 -0
  136. package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
  137. package/redscript-docs/docs/en/stdlib/state.md +142 -0
  138. package/redscript-docs/docs/en/stdlib/strings.md +154 -0
  139. package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
  140. package/redscript-docs/docs/en/stdlib/teams.md +153 -0
  141. package/redscript-docs/docs/en/stdlib/timer.md +246 -0
  142. package/redscript-docs/docs/en/stdlib/vec.md +158 -0
  143. package/redscript-docs/docs/en/stdlib/world.md +298 -0
  144. package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
  145. package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
  146. package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
  147. package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
  148. package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
  149. package/redscript-docs/docs/zh/stdlib/color.md +353 -0
  150. package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
  151. package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
  152. package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
  153. package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
  154. package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
  155. package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
  156. package/redscript-docs/docs/zh/stdlib/events.md +3 -0
  157. package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
  158. package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
  159. package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
  160. package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
  161. package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
  162. package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
  163. package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
  164. package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
  165. package/redscript-docs/docs/zh/stdlib/list.md +561 -0
  166. package/redscript-docs/docs/zh/stdlib/map.md +132 -0
  167. package/redscript-docs/docs/zh/stdlib/math.md +193 -0
  168. package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
  169. package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
  170. package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
  171. package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
  172. package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
  173. package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
  174. package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
  175. package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
  176. package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
  177. package/redscript-docs/docs/zh/stdlib/player.md +78 -0
  178. package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
  179. package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
  180. package/redscript-docs/docs/zh/stdlib/random.md +222 -0
  181. package/redscript-docs/docs/zh/stdlib/result.md +147 -0
  182. package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
  183. package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
  184. package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
  185. package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
  186. package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
  187. package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
  188. package/redscript-docs/docs/zh/stdlib/state.md +134 -0
  189. package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
  190. package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
  191. package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
  192. package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
  193. package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
  194. package/redscript-docs/docs/zh/stdlib/world.md +289 -0
  195. package/src/__tests__/formatter-extra.test.ts +139 -0
  196. package/src/__tests__/global-vars.test.ts +171 -0
  197. package/src/__tests__/lint/new-rules.test.ts +437 -0
  198. package/src/__tests__/lsp-rename.test.ts +171 -0
  199. package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
  200. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
  201. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
  202. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
  203. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
  204. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
  205. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
  206. package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
  207. package/src/__tests__/mc-syntax.test.ts +3 -0
  208. package/src/__tests__/monomorphize-coverage.test.ts +220 -0
  209. package/src/__tests__/optimizer-cse.test.ts +250 -0
  210. package/src/__tests__/parser.test.ts +4 -13
  211. package/src/__tests__/repl-server-extra.test.ts +6 -6
  212. package/src/__tests__/repl-server.test.ts +5 -6
  213. package/src/__tests__/stdlib/queue.test.ts +6 -6
  214. package/src/lexer/index.ts +2 -1
  215. package/src/lint/index.ts +713 -5
  216. package/src/mc-test/client.ts +40 -0
  217. package/src/mir/lower.ts +111 -2
  218. package/src/optimizer/interprocedural.ts +40 -2
  219. package/src/parser/decl-parser.ts +349 -0
  220. package/src/parser/expr-parser.ts +838 -0
  221. package/src/parser/index.ts +17 -2558
  222. package/src/parser/stmt-parser.ts +585 -0
  223. package/src/parser/type-parser.ts +276 -0
  224. package/src/parser/utils.ts +173 -0
  225. package/src/stdlib/queue.mcrs +19 -6
@@ -0,0 +1,437 @@
1
+ /**
2
+ * Tests for the 5 new redscript lint rules:
3
+ * no-dead-assignment
4
+ * prefer-match-exhaustive
5
+ * no-empty-catch
6
+ * naming-convention
7
+ * no-magic-numbers
8
+ */
9
+
10
+ import { lintString } from '../../lint/index'
11
+
12
+ function lint(source: string, opts: { maxFunctionLines?: number; allowedNumbers?: number[] } = {}) {
13
+ return lintString(source, '<test>', 'test', opts)
14
+ }
15
+
16
+ function warnings(source: string, rule: string, opts: { allowedNumbers?: number[] } = {}) {
17
+ return lint(source, opts).filter(w => w.rule === rule)
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Rule: no-dead-assignment
22
+ // ---------------------------------------------------------------------------
23
+
24
+ describe('no-dead-assignment', () => {
25
+ it('warns when a variable is assigned then immediately overwritten without being read', () => {
26
+ const src = `
27
+ fn foo(): void {
28
+ let x: int = 0;
29
+ x = 5;
30
+ x = 10;
31
+ say("done");
32
+ }
33
+ `
34
+ const w = warnings(src, 'no-dead-assignment')
35
+ expect(w.length).toBeGreaterThan(0)
36
+ expect(w[0].message).toContain('"x"')
37
+ expect(w[0].message).toContain('never read')
38
+ })
39
+
40
+ it('does NOT warn when each assignment is read before the next one', () => {
41
+ // x is read (return x + y) after all assignments
42
+ // y is read (return x + y)
43
+ const src = `
44
+ fn foo(): int {
45
+ let x: int = 5;
46
+ let y: int = x;
47
+ x = 10;
48
+ return x + y;
49
+ }
50
+ `
51
+ const w = warnings(src, 'no-dead-assignment')
52
+ // x is not overwritten without being read between assignments here:
53
+ // let x=5 → pending; y reads x → pending cleared; x=10 → no prior pending; return x reads x
54
+ expect(w).toHaveLength(0)
55
+ })
56
+
57
+ it('warns when let init is immediately overwritten without being read', () => {
58
+ // The initial let value is dead because x is reassigned before any read
59
+ const src = `
60
+ fn foo(): int {
61
+ let x: int = 0;
62
+ x = 42;
63
+ return x;
64
+ }
65
+ `
66
+ // let x=0 is dead (overwritten by x=42 before x is read)
67
+ const w = warnings(src, 'no-dead-assignment')
68
+ expect(w.length).toBeGreaterThan(0)
69
+ expect(w[0].message).toContain('"x"')
70
+ })
71
+
72
+ it('warns for multiple dead assignments in the same function', () => {
73
+ const src = `
74
+ fn bar(): void {
75
+ let a: int = 0;
76
+ let b: int = 0;
77
+ a = 1;
78
+ a = 2;
79
+ b = 3;
80
+ b = 4;
81
+ say("done");
82
+ }
83
+ `
84
+ const w = warnings(src, 'no-dead-assignment')
85
+ const msgs = w.map(warn => warn.message)
86
+ expect(msgs.some(m => m.includes('"a"'))).toBe(true)
87
+ expect(msgs.some(m => m.includes('"b"'))).toBe(true)
88
+ })
89
+ })
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Rule: prefer-match-exhaustive
93
+ // ---------------------------------------------------------------------------
94
+
95
+ describe('prefer-match-exhaustive', () => {
96
+ it('warns when Option match is missing the None arm', () => {
97
+ const src = `
98
+ fn foo(x: Option<int>): void {
99
+ match x {
100
+ Some(v) => { say("got value"); }
101
+ }
102
+ }
103
+ `
104
+ const w = warnings(src, 'prefer-match-exhaustive')
105
+ expect(w.length).toBeGreaterThan(0)
106
+ expect(w.some(warn => warn.message.includes('None'))).toBe(true)
107
+ })
108
+
109
+ it('warns when Option match is missing the Some arm', () => {
110
+ const src = `
111
+ fn foo(x: Option<int>): void {
112
+ match x {
113
+ None => { say("nothing"); }
114
+ }
115
+ }
116
+ `
117
+ const w = warnings(src, 'prefer-match-exhaustive')
118
+ expect(w.length).toBeGreaterThan(0)
119
+ expect(w.some(warn => warn.message.includes('Some'))).toBe(true)
120
+ })
121
+
122
+ it('does NOT warn when both Some and None arms are present', () => {
123
+ const src = `
124
+ fn foo(x: Option<int>): void {
125
+ match x {
126
+ Some(v) => { say("got value"); }
127
+ None => { say("nothing"); }
128
+ }
129
+ }
130
+ `
131
+ const w = warnings(src, 'prefer-match-exhaustive')
132
+ expect(w).toHaveLength(0)
133
+ })
134
+
135
+ it('does NOT warn when a wildcard arm is present', () => {
136
+ const src = `
137
+ fn foo(x: Option<int>): void {
138
+ match x {
139
+ Some(v) => { say("got"); }
140
+ _ => { say("other"); }
141
+ }
142
+ }
143
+ `
144
+ const w = warnings(src, 'prefer-match-exhaustive')
145
+ expect(w).toHaveLength(0)
146
+ })
147
+
148
+ it('does NOT warn for non-Option integer match', () => {
149
+ const src = `
150
+ fn foo(x: int): void {
151
+ match x {
152
+ 1 => { say("one"); }
153
+ 2 => { say("two"); }
154
+ _ => { say("other"); }
155
+ }
156
+ }
157
+ `
158
+ const w = warnings(src, 'prefer-match-exhaustive')
159
+ expect(w).toHaveLength(0)
160
+ })
161
+ })
162
+
163
+ // ---------------------------------------------------------------------------
164
+ // Rule: no-empty-catch
165
+ // ---------------------------------------------------------------------------
166
+
167
+ describe('no-empty-catch', () => {
168
+ it('warns when if let Some has an empty else block', () => {
169
+ const src = `
170
+ fn foo(x: Option<int>): void {
171
+ if let Some(v) = x {
172
+ say("got it");
173
+ } else {}
174
+ }
175
+ `
176
+ const w = warnings(src, 'no-empty-catch')
177
+ expect(w.length).toBeGreaterThan(0)
178
+ expect(w[0].message).toContain('None case is silently ignored')
179
+ })
180
+
181
+ it('does NOT warn when if let Some has no else block', () => {
182
+ const src = `
183
+ fn foo(x: Option<int>): void {
184
+ if let Some(v) = x {
185
+ say("got it");
186
+ }
187
+ }
188
+ `
189
+ const w = warnings(src, 'no-empty-catch')
190
+ expect(w).toHaveLength(0)
191
+ })
192
+
193
+ it('does NOT warn when else block has statements', () => {
194
+ const src = `
195
+ fn foo(x: Option<int>): void {
196
+ if let Some(v) = x {
197
+ say("got it");
198
+ } else {
199
+ say("nothing");
200
+ }
201
+ }
202
+ `
203
+ const w = warnings(src, 'no-empty-catch')
204
+ expect(w).toHaveLength(0)
205
+ })
206
+
207
+ it('warns when a match arm body is empty', () => {
208
+ const src = `
209
+ fn foo(x: Option<int>): void {
210
+ match x {
211
+ Some(v) => { say("ok"); }
212
+ None => {}
213
+ }
214
+ }
215
+ `
216
+ const w = warnings(src, 'no-empty-catch')
217
+ expect(w.length).toBeGreaterThan(0)
218
+ expect(w[0].message).toContain('Empty match arm')
219
+ })
220
+
221
+ it('does NOT warn when all match arm bodies have statements', () => {
222
+ const src = `
223
+ fn foo(x: Option<int>): void {
224
+ match x {
225
+ Some(v) => { say("ok"); }
226
+ None => { say("none"); }
227
+ }
228
+ }
229
+ `
230
+ const w = warnings(src, 'no-empty-catch')
231
+ expect(w).toHaveLength(0)
232
+ })
233
+ })
234
+
235
+ // ---------------------------------------------------------------------------
236
+ // Rule: naming-convention
237
+ // ---------------------------------------------------------------------------
238
+
239
+ describe('naming-convention', () => {
240
+ it('warns for snake_case variable names', () => {
241
+ const src = `
242
+ fn foo(): void {
243
+ let my_var: int = 5;
244
+ say("ok");
245
+ }
246
+ `
247
+ const w = warnings(src, 'naming-convention')
248
+ expect(w.length).toBeGreaterThan(0)
249
+ expect(w[0].message).toContain('"my_var"')
250
+ expect(w[0].message).toContain('camelCase')
251
+ })
252
+
253
+ it('warns for UPPER_CASE variable names', () => {
254
+ const src = `
255
+ fn foo(): void {
256
+ let MAX_VAL: int = 5;
257
+ say("ok");
258
+ }
259
+ `
260
+ const w = warnings(src, 'naming-convention')
261
+ expect(w.length).toBeGreaterThan(0)
262
+ expect(w[0].message).toContain('"MAX_VAL"')
263
+ })
264
+
265
+ it('does NOT warn for camelCase variable names', () => {
266
+ const src = `
267
+ fn foo(): void {
268
+ let myVar: int = 5;
269
+ let anotherOne: int = 10;
270
+ say("ok");
271
+ }
272
+ `
273
+ const w = warnings(src, 'naming-convention')
274
+ expect(w).toHaveLength(0)
275
+ })
276
+
277
+ it('does NOT warn for single-letter variable names', () => {
278
+ const src = `
279
+ fn foo(): int {
280
+ let x: int = 5;
281
+ return x;
282
+ }
283
+ `
284
+ const w = warnings(src, 'naming-convention')
285
+ expect(w).toHaveLength(0)
286
+ })
287
+
288
+ it('warns for snake_case loop bindings in foreach', () => {
289
+ const src = `
290
+ fn foo(): void {
291
+ foreach (my_item in @a) {
292
+ say("x");
293
+ }
294
+ }
295
+ `
296
+ const w = warnings(src, 'naming-convention')
297
+ expect(w.length).toBeGreaterThan(0)
298
+ expect(w[0].message).toContain('"my_item"')
299
+ })
300
+
301
+ it('does NOT warn for camelCase loop bindings', () => {
302
+ const src = `
303
+ fn foo(): void {
304
+ foreach (myItem in @a) {
305
+ say("x");
306
+ }
307
+ }
308
+ `
309
+ const w = warnings(src, 'naming-convention')
310
+ expect(w).toHaveLength(0)
311
+ })
312
+
313
+ it('warns for struct with lowercase start name', () => {
314
+ const src = `
315
+ struct myStruct { x: int }
316
+
317
+ fn foo(): void {
318
+ say("ok");
319
+ }
320
+ `
321
+ const w = warnings(src, 'naming-convention')
322
+ expect(w.length).toBeGreaterThan(0)
323
+ expect(w[0].message).toContain('"myStruct"')
324
+ expect(w[0].message).toContain('PascalCase')
325
+ })
326
+
327
+ it('does NOT warn for correctly named PascalCase struct', () => {
328
+ const src = `
329
+ struct MyStruct { x: int }
330
+
331
+ fn foo(): void {
332
+ say("ok");
333
+ }
334
+ `
335
+ const w = warnings(src, 'naming-convention')
336
+ expect(w).toHaveLength(0)
337
+ })
338
+
339
+ it('allows leading underscore in variable names', () => {
340
+ const src = `
341
+ fn foo(): void {
342
+ let _unused: int = 5;
343
+ say("ok");
344
+ }
345
+ `
346
+ const w = warnings(src, 'naming-convention')
347
+ expect(w).toHaveLength(0)
348
+ })
349
+ })
350
+
351
+ // ---------------------------------------------------------------------------
352
+ // Rule: no-magic-numbers
353
+ // ---------------------------------------------------------------------------
354
+
355
+ describe('no-magic-numbers', () => {
356
+ it('warns for literal numbers other than 0 and 1', () => {
357
+ const src = `
358
+ fn foo(): void {
359
+ let x: int = 42;
360
+ say("ok");
361
+ }
362
+ `
363
+ const w = warnings(src, 'no-magic-numbers')
364
+ expect(w.length).toBeGreaterThan(0)
365
+ expect(w[0].message).toContain('42')
366
+ expect(w[0].message).toContain('Magic number')
367
+ })
368
+
369
+ it('does NOT warn for 0', () => {
370
+ const src = `
371
+ fn foo(): int {
372
+ let x: int = 0;
373
+ return x;
374
+ }
375
+ `
376
+ const w = warnings(src, 'no-magic-numbers')
377
+ expect(w).toHaveLength(0)
378
+ })
379
+
380
+ it('does NOT warn for 1', () => {
381
+ const src = `
382
+ fn foo(): int {
383
+ let x: int = 1;
384
+ return x;
385
+ }
386
+ `
387
+ const w = warnings(src, 'no-magic-numbers')
388
+ expect(w).toHaveLength(0)
389
+ })
390
+
391
+ it('does NOT warn for numbers in const declarations', () => {
392
+ const src = `
393
+ const MAX_SIZE: int = 100;
394
+
395
+ fn foo(): void {
396
+ say("ok");
397
+ }
398
+ `
399
+ const w = warnings(src, 'no-magic-numbers')
400
+ expect(w).toHaveLength(0)
401
+ })
402
+
403
+ it('respects custom allowedNumbers list', () => {
404
+ const src = `
405
+ fn foo(): void {
406
+ let x: int = 42;
407
+ let y: int = 100;
408
+ say("ok");
409
+ }
410
+ `
411
+ // With 42 allowed, only 100 should warn
412
+ const w = warnings(src, 'no-magic-numbers', { allowedNumbers: [0, 1, 42] })
413
+ expect(w).toHaveLength(1)
414
+ expect(w[0].message).toContain('100')
415
+ })
416
+
417
+ it('warns for magic numbers used in expressions', () => {
418
+ const src = `
419
+ fn foo(x: int): bool {
420
+ return x > 255;
421
+ }
422
+ `
423
+ const w = warnings(src, 'no-magic-numbers')
424
+ expect(w.length).toBeGreaterThan(0)
425
+ expect(w[0].message).toContain('255')
426
+ })
427
+
428
+ it('does NOT warn for 0 and 1 even without explicit allowedNumbers', () => {
429
+ const src = `
430
+ fn foo(x: int): bool {
431
+ return x > 0 && x < 1;
432
+ }
433
+ `
434
+ const w = warnings(src, 'no-magic-numbers')
435
+ expect(w).toHaveLength(0)
436
+ })
437
+ })
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Tests for src/lsp/rename.ts — LSP rename symbol support
3
+ *
4
+ * Covers: findRenameRanges, buildRenameWorkspaceEdit,
5
+ * local variable renaming, function renaming, field renaming,
6
+ * nested scopes, and edge cases.
7
+ */
8
+
9
+ import { Lexer } from '../lexer'
10
+ import { Parser } from '../parser'
11
+ import { findRenameRanges, buildRenameWorkspaceEdit } from '../lsp/rename'
12
+ import { TextDocument } from 'vscode-languageserver-textdocument'
13
+ import type { Program } from '../ast/types'
14
+ import type { Position } from 'vscode-languageserver/node'
15
+
16
+ function parse(source: string): Program {
17
+ const tokens = new Lexer(source).tokenize()
18
+ return new Parser(tokens, source).parse('test')
19
+ }
20
+
21
+ function pos(line: number, character: number): Position {
22
+ return { line, character }
23
+ }
24
+
25
+ describe('findRenameRanges — local variables', () => {
26
+ it('finds all occurrences of a local variable', () => {
27
+ const src = 'fn test(): void {\n let x: int = 1;\n let y: int = x + 2;\n}'
28
+ const program = parse(src)
29
+ // Position on 'x' in 'let x: int = 1' (line 1, char 6)
30
+ const ranges = findRenameRanges(src, program, pos(1, 6))
31
+ expect(ranges.length).toBe(2) // declaration + usage
32
+ })
33
+
34
+ it('returns empty for position not on a symbol', () => {
35
+ const src = 'fn test(): void {\n let x: int = 1;\n}'
36
+ const program = parse(src)
37
+ // Position on whitespace
38
+ const ranges = findRenameRanges(src, program, pos(1, 0))
39
+ expect(ranges).toEqual([])
40
+ })
41
+
42
+ it('handles variable used multiple times', () => {
43
+ const src = 'fn test(): void {\n let x: int = 1;\n let a: int = x;\n let b: int = x;\n let c: int = x;\n}'
44
+ const program = parse(src)
45
+ const ranges = findRenameRanges(src, program, pos(1, 6))
46
+ expect(ranges.length).toBe(4) // 1 decl + 3 uses
47
+ })
48
+ })
49
+
50
+ describe('findRenameRanges — functions', () => {
51
+ it('finds function name at declaration', () => {
52
+ const src = 'fn greet(): void {\n}\nfn main(): void {\n greet();\n}'
53
+ const program = parse(src)
54
+ // Position on 'greet' in fn declaration (line 0, char 3)
55
+ const ranges = findRenameRanges(src, program, pos(0, 3))
56
+ expect(ranges.length).toBe(2) // declaration + call
57
+ })
58
+
59
+ it('finds function name at call site', () => {
60
+ const src = 'fn greet(): void {\n}\nfn main(): void {\n greet();\n}'
61
+ const program = parse(src)
62
+ // Position on 'greet()' call (line 3, char 2)
63
+ const ranges = findRenameRanges(src, program, pos(3, 2))
64
+ expect(ranges.length).toBe(2)
65
+ })
66
+ })
67
+
68
+ describe('findRenameRanges — struct fields', () => {
69
+ it('finds field in struct declaration and member access', () => {
70
+ const src = [
71
+ 'struct Point {',
72
+ ' x: int,',
73
+ ' y: int,',
74
+ '}',
75
+ 'fn test(): Point {',
76
+ ' let p: Point = Point { x: 1, y: 2 };',
77
+ ' let v: int = p.x;',
78
+ ' return Point { x: v, y: 0 };',
79
+ '}',
80
+ ].join('\n')
81
+ const program = parse(src)
82
+ // Position on 'x' in struct field declaration (line 1, char 2)
83
+ const ranges = findRenameRanges(src, program, pos(1, 2))
84
+ // Should find: field decl, struct literal 'x: 1', 'p.x', and struct literal 'x: v'
85
+ expect(ranges.length).toBeGreaterThanOrEqual(2)
86
+ })
87
+ })
88
+
89
+ describe('findRenameRanges — nested scopes', () => {
90
+ it('distinguishes variables in different scopes', () => {
91
+ const src = [
92
+ 'fn test(): void {',
93
+ ' let x: int = 1;',
94
+ ' if true {',
95
+ ' let x: int = 2;',
96
+ ' let a: int = x;',
97
+ ' }',
98
+ ' let b: int = x;',
99
+ '}',
100
+ ].join('\n')
101
+ const program = parse(src)
102
+ // Position on outer x (line 1, char 6)
103
+ const outerRanges = findRenameRanges(src, program, pos(1, 6))
104
+ // Position on inner x (line 3, char 8)
105
+ const innerRanges = findRenameRanges(src, program, pos(3, 8))
106
+ // They should refer to different symbols with different occurrence counts
107
+ expect(outerRanges.length).not.toBe(0)
108
+ expect(innerRanges.length).not.toBe(0)
109
+ })
110
+ })
111
+
112
+ describe('findRenameRanges — parameters', () => {
113
+ it('finds function parameter occurrences', () => {
114
+ const src = 'fn add(a: int, b: int): int {\n return a + b;\n}'
115
+ const program = parse(src)
116
+ // Position on parameter 'a' (line 0, char 7)
117
+ const ranges = findRenameRanges(src, program, pos(0, 7))
118
+ expect(ranges.length).toBe(2) // param decl + usage in return
119
+ })
120
+ })
121
+
122
+ describe('buildRenameWorkspaceEdit', () => {
123
+ it('returns null when position is not on a symbol', () => {
124
+ const src = 'fn test(): void {}'
125
+ const program = parse(src)
126
+ const doc = TextDocument.create('file:///test.mcrs', 'redscript', 1, src)
127
+ const edit = buildRenameWorkspaceEdit(doc, program, pos(0, 0), 'newName')
128
+ // 'fn' keyword at pos(0,0) — might or might not resolve
129
+ // If no symbol, should return null
130
+ if (edit === null) {
131
+ expect(edit).toBeNull()
132
+ } else {
133
+ expect(edit.changes).toBeDefined()
134
+ }
135
+ })
136
+
137
+ it('returns workspace edit with correct URI and new name', () => {
138
+ const src = 'fn test(): void {\n let x: int = 1;\n let y: int = x;\n}'
139
+ const program = parse(src)
140
+ const doc = TextDocument.create('file:///test.mcrs', 'redscript', 1, src)
141
+ const edit = buildRenameWorkspaceEdit(doc, program, pos(1, 6), 'newVar')
142
+ expect(edit).not.toBeNull()
143
+ expect(edit!.changes).toBeDefined()
144
+ const changes = edit!.changes!['file:///test.mcrs']
145
+ expect(changes.length).toBe(2)
146
+ expect(changes.every(c => c.newText === 'newVar')).toBe(true)
147
+ })
148
+ })
149
+
150
+ describe('findRenameRanges — edge cases', () => {
151
+ it('handles single function with no body statements', () => {
152
+ const src = 'fn empty(): void {}'
153
+ const program = parse(src)
154
+ const ranges = findRenameRanges(src, program, pos(0, 3))
155
+ expect(ranges.length).toBe(1) // just the declaration
156
+ })
157
+
158
+ it('handles const declarations', () => {
159
+ const src = 'fn test(): void {\n const MAX: int = 100;\n let x: int = MAX;\n}'
160
+ const program = parse(src)
161
+ const ranges = findRenameRanges(src, program, pos(1, 8))
162
+ expect(ranges.length).toBe(2) // decl + use
163
+ })
164
+
165
+ it('handles assign expressions', () => {
166
+ const src = 'fn test(): void {\n let x: int = 1;\n x = 2;\n let y: int = x;\n}'
167
+ const program = parse(src)
168
+ const ranges = findRenameRanges(src, program, pos(1, 6))
169
+ expect(ranges.length).toBe(3) // decl + assign + use
170
+ })
171
+ })