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
package/src/lint/index.ts CHANGED
@@ -5,11 +5,16 @@
5
5
  * Run after HIR lowering, before MIR.
6
6
  *
7
7
  * Rules:
8
- * unused-variable — let x = 5 but x never read
9
- * unused-import — import math::sin but sin never called
10
- * magic-number — literal number > 1 used directly (0 and 1 ignored)
11
- * dead-branch — if (const == const) always-true/false condition
12
- * function-too-long — function body exceeds 50 lines
8
+ * unused-variable — let x = 5 but x never read
9
+ * unused-import — import math::sin but sin never called
10
+ * magic-number — literal number > 1 used directly (0 and 1 ignored)
11
+ * dead-branch — if (const == const) always-true/false condition
12
+ * function-too-long — function body exceeds 50 lines
13
+ * no-dead-assignment — variable assigned but never read after the assignment
14
+ * prefer-match-exhaustive — Option match missing Some or None arm
15
+ * no-empty-catch — empty else branch in if_let_some (silent failure)
16
+ * naming-convention — variables must be camelCase; types must be PascalCase
17
+ * no-magic-numbers — any literal number other than 0 or 1 used in an expression
13
18
  */
14
19
 
15
20
  import type {
@@ -42,6 +47,8 @@ export interface LintOptions {
42
47
  filePath?: string
43
48
  /** Max function body lines before function-too-long fires (default: 50) */
44
49
  maxFunctionLines?: number
50
+ /** Allowed literal number values for no-magic-numbers (default: [0, 1]) */
51
+ allowedNumbers?: number[]
45
52
  }
46
53
 
47
54
  // ---------------------------------------------------------------------------
@@ -61,6 +68,7 @@ export function lintSource(
61
68
  const warnings: LintWarning[] = []
62
69
  const file = options.filePath
63
70
  const maxLines = options.maxFunctionLines ?? 50
71
+ const allowedNumbers = options.allowedNumbers ?? [0, 1]
64
72
 
65
73
  // Rule: unused-import
66
74
  warnings.push(...checkUnusedImports(imports, hir, file))
@@ -80,8 +88,26 @@ export function lintSource(
80
88
  // Rule: function-too-long
81
89
  const fnWarning = checkFunctionLength(fn, maxLines, file)
82
90
  if (fnWarning) warnings.push(fnWarning)
91
+
92
+ // Rule: no-dead-assignment
93
+ warnings.push(...checkNoDeadAssignment(fn, file))
94
+
95
+ // Rule: prefer-match-exhaustive
96
+ warnings.push(...checkPreferMatchExhaustive(fn, file))
97
+
98
+ // Rule: no-empty-catch
99
+ warnings.push(...checkNoEmptyCatch(fn, file))
100
+
101
+ // Rule: naming-convention
102
+ warnings.push(...checkNamingConvention(fn, file))
103
+
104
+ // Rule: no-magic-numbers
105
+ warnings.push(...checkNoMagicNumbers(fn, allowedNumbers, file))
83
106
  }
84
107
 
108
+ // Rule: naming-convention — type names in structs/enums
109
+ warnings.push(...checkNamingConventionModule(hir, file))
110
+
85
111
  return warnings
86
112
  }
87
113
 
@@ -920,3 +946,685 @@ function countStmts(block: HIRBlock): number {
920
946
  }
921
947
  return count
922
948
  }
949
+
950
+ // ---------------------------------------------------------------------------
951
+ // Rule: no-dead-assignment
952
+ // ---------------------------------------------------------------------------
953
+ //
954
+ // Detects variables that are assigned (via `assign` expr) but whose value is
955
+ // never subsequently read. This is stricter than unused-variable: the variable
956
+ // IS read at some point, but a particular write is "dead" because it is
957
+ // overwritten before being read.
958
+ //
959
+ // Implementation: single-pass, scope-insensitive. We track every assignment
960
+ // target and warn if the same name is assigned twice with no intervening read.
961
+
962
+ function checkNoDeadAssignment(fn: HIRFunction, file?: string): LintWarning[] {
963
+ const warnings: LintWarning[] = []
964
+ // Map from name → span of the last unread assignment
965
+ const pendingWrite = new Map<string, { span?: Span }>()
966
+ noDeadAssignBlock(fn.body, pendingWrite, warnings, file)
967
+ return warnings
968
+ }
969
+
970
+ function noDeadAssignBlock(
971
+ block: HIRBlock,
972
+ pending: Map<string, { span?: Span }>,
973
+ out: LintWarning[],
974
+ file: string | undefined,
975
+ ): void {
976
+ for (const stmt of block) {
977
+ noDeadAssignStmt(stmt, pending, out, file)
978
+ }
979
+ }
980
+
981
+ function noDeadAssignStmt(
982
+ stmt: HIRStmt,
983
+ pending: Map<string, { span?: Span }>,
984
+ out: LintWarning[],
985
+ file: string | undefined,
986
+ ): void {
987
+ switch (stmt.kind) {
988
+ case 'let':
989
+ // Initial let binding — register as pending write
990
+ noDeadAssignExprReads(stmt.init, pending, out, file)
991
+ pending.set(stmt.name, { span: stmt.span })
992
+ break
993
+ case 'let_destruct':
994
+ noDeadAssignExprReads(stmt.init, pending, out, file)
995
+ for (const name of stmt.names) pending.set(name, { span: stmt.span })
996
+ break
997
+ case 'const_decl':
998
+ noDeadAssignExprReads(stmt.value, pending, out, file)
999
+ break
1000
+ case 'expr':
1001
+ noDeadAssignExprReads(stmt.expr, pending, out, file)
1002
+ break
1003
+ case 'return':
1004
+ if (stmt.value) noDeadAssignExprReads(stmt.value, pending, out, file)
1005
+ break
1006
+ case 'if':
1007
+ noDeadAssignExprReads(stmt.cond, pending, out, file)
1008
+ noDeadAssignBlock(stmt.then, pending, out, file)
1009
+ if (stmt.else_) noDeadAssignBlock(stmt.else_, pending, out, file)
1010
+ break
1011
+ case 'while':
1012
+ noDeadAssignExprReads(stmt.cond, pending, out, file)
1013
+ noDeadAssignBlock(stmt.body, pending, out, file)
1014
+ if (stmt.step) noDeadAssignBlock(stmt.step, pending, out, file)
1015
+ break
1016
+ case 'foreach':
1017
+ noDeadAssignExprReads(stmt.iterable, pending, out, file)
1018
+ noDeadAssignBlock(stmt.body, pending, out, file)
1019
+ break
1020
+ case 'match':
1021
+ noDeadAssignExprReads(stmt.expr, pending, out, file)
1022
+ for (const arm of stmt.arms) noDeadAssignBlock(arm.body, pending, out, file)
1023
+ break
1024
+ case 'execute':
1025
+ noDeadAssignBlock(stmt.body, pending, out, file)
1026
+ break
1027
+ case 'if_let_some':
1028
+ noDeadAssignExprReads(stmt.init, pending, out, file)
1029
+ noDeadAssignBlock(stmt.then, pending, out, file)
1030
+ if (stmt.else_) noDeadAssignBlock(stmt.else_, pending, out, file)
1031
+ break
1032
+ case 'while_let_some':
1033
+ noDeadAssignExprReads(stmt.init, pending, out, file)
1034
+ noDeadAssignBlock(stmt.body, pending, out, file)
1035
+ break
1036
+ case 'labeled_loop':
1037
+ noDeadAssignStmt(stmt.body, pending, out, file)
1038
+ break
1039
+ }
1040
+ }
1041
+
1042
+ /** Processes an expression: reads clear pending writes; assign exprs register new pending writes. */
1043
+ function noDeadAssignExprReads(
1044
+ expr: HIRExpr | undefined,
1045
+ pending: Map<string, { span?: Span }>,
1046
+ out: LintWarning[],
1047
+ file: string | undefined,
1048
+ ): void {
1049
+ if (!expr) return
1050
+ switch (expr.kind) {
1051
+ case 'ident':
1052
+ // Reading a variable clears its pending write
1053
+ pending.delete(expr.name)
1054
+ break
1055
+ case 'assign': {
1056
+ // RHS is read first
1057
+ noDeadAssignExprReads(expr.value, pending, out, file)
1058
+ // Then the target is written — if there's already a pending unread write, warn
1059
+ if (pending.has(expr.target)) {
1060
+ const prev = pending.get(expr.target)!
1061
+ const warn: LintWarning = {
1062
+ rule: 'no-dead-assignment',
1063
+ message: `Assignment to "${expr.target}" is never read before being overwritten`,
1064
+ file,
1065
+ }
1066
+ if (prev.span) { warn.line = prev.span.line; warn.col = prev.span.col }
1067
+ out.push(warn)
1068
+ }
1069
+ pending.set(expr.target, { span: expr.span })
1070
+ break
1071
+ }
1072
+ case 'binary':
1073
+ noDeadAssignExprReads(expr.left, pending, out, file)
1074
+ noDeadAssignExprReads(expr.right, pending, out, file)
1075
+ break
1076
+ case 'unary':
1077
+ noDeadAssignExprReads(expr.operand, pending, out, file)
1078
+ break
1079
+ case 'call':
1080
+ for (const arg of expr.args) noDeadAssignExprReads(arg, pending, out, file)
1081
+ break
1082
+ case 'invoke':
1083
+ noDeadAssignExprReads(expr.callee, pending, out, file)
1084
+ for (const arg of expr.args) noDeadAssignExprReads(arg, pending, out, file)
1085
+ break
1086
+ case 'static_call':
1087
+ for (const arg of expr.args) noDeadAssignExprReads(arg, pending, out, file)
1088
+ break
1089
+ case 'member':
1090
+ noDeadAssignExprReads(expr.obj, pending, out, file)
1091
+ break
1092
+ case 'member_assign':
1093
+ noDeadAssignExprReads(expr.obj, pending, out, file)
1094
+ noDeadAssignExprReads(expr.value, pending, out, file)
1095
+ break
1096
+ case 'index':
1097
+ noDeadAssignExprReads(expr.obj, pending, out, file)
1098
+ noDeadAssignExprReads(expr.index, pending, out, file)
1099
+ break
1100
+ case 'index_assign':
1101
+ noDeadAssignExprReads(expr.obj, pending, out, file)
1102
+ noDeadAssignExprReads(expr.index, pending, out, file)
1103
+ noDeadAssignExprReads(expr.value, pending, out, file)
1104
+ break
1105
+ case 'some_lit':
1106
+ noDeadAssignExprReads(expr.value, pending, out, file)
1107
+ break
1108
+ case 'unwrap_or':
1109
+ noDeadAssignExprReads(expr.opt, pending, out, file)
1110
+ noDeadAssignExprReads(expr.default_, pending, out, file)
1111
+ break
1112
+ case 'type_cast':
1113
+ noDeadAssignExprReads(expr.expr, pending, out, file)
1114
+ break
1115
+ case 'array_lit':
1116
+ case 'tuple_lit':
1117
+ for (const e of expr.elements) noDeadAssignExprReads(e, pending, out, file)
1118
+ break
1119
+ case 'struct_lit':
1120
+ for (const f of expr.fields) noDeadAssignExprReads(f.value, pending, out, file)
1121
+ break
1122
+ case 'str_interp':
1123
+ for (const p of expr.parts) {
1124
+ if (typeof p !== 'string') noDeadAssignExprReads(p, pending, out, file)
1125
+ }
1126
+ break
1127
+ case 'f_string':
1128
+ for (const p of expr.parts) {
1129
+ if (p.kind === 'expr') noDeadAssignExprReads(p.expr, pending, out, file)
1130
+ }
1131
+ break
1132
+ case 'lambda':
1133
+ if (Array.isArray(expr.body)) {
1134
+ noDeadAssignBlock(expr.body as HIRBlock, pending, out, file)
1135
+ } else {
1136
+ noDeadAssignExprReads(expr.body as HIRExpr, pending, out, file)
1137
+ }
1138
+ break
1139
+ case 'enum_construct':
1140
+ for (const f of expr.args) noDeadAssignExprReads(f.value, pending, out, file)
1141
+ break
1142
+ default:
1143
+ break
1144
+ }
1145
+ }
1146
+
1147
+ // ---------------------------------------------------------------------------
1148
+ // Rule: prefer-match-exhaustive
1149
+ // ---------------------------------------------------------------------------
1150
+ //
1151
+ // When a match statement uses Option patterns (PatSome / PatNone), it should
1152
+ // cover both arms. Missing PatNone means the None case falls through silently;
1153
+ // missing PatSome means Some values are unhandled.
1154
+
1155
+ function checkPreferMatchExhaustive(fn: HIRFunction, file?: string): LintWarning[] {
1156
+ const warnings: LintWarning[] = []
1157
+ checkPreferMatchExhaustiveBlock(fn.body, warnings, file)
1158
+ return warnings
1159
+ }
1160
+
1161
+ function checkPreferMatchExhaustiveBlock(block: HIRBlock, out: LintWarning[], file: string | undefined): void {
1162
+ for (const stmt of block) {
1163
+ checkPreferMatchExhaustiveStmt(stmt, out, file)
1164
+ }
1165
+ }
1166
+
1167
+ function checkPreferMatchExhaustiveStmt(stmt: HIRStmt, out: LintWarning[], file: string | undefined): void {
1168
+ switch (stmt.kind) {
1169
+ case 'match': {
1170
+ const patKinds = new Set(stmt.arms.map(a => a.pattern.kind))
1171
+ const hasOptionPat = patKinds.has('PatSome') || patKinds.has('PatNone')
1172
+ const hasWild = patKinds.has('PatWild')
1173
+ if (hasOptionPat && !hasWild) {
1174
+ const hasSome = patKinds.has('PatSome')
1175
+ const hasNone = patKinds.has('PatNone')
1176
+ if (!hasSome) {
1177
+ const warn: LintWarning = {
1178
+ rule: 'prefer-match-exhaustive',
1179
+ message: `match on Option is missing a Some(_) arm`,
1180
+ file,
1181
+ }
1182
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1183
+ out.push(warn)
1184
+ }
1185
+ if (!hasNone) {
1186
+ const warn: LintWarning = {
1187
+ rule: 'prefer-match-exhaustive',
1188
+ message: `match on Option is missing a None arm`,
1189
+ file,
1190
+ }
1191
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1192
+ out.push(warn)
1193
+ }
1194
+ }
1195
+ // Recurse into arm bodies
1196
+ for (const arm of stmt.arms) checkPreferMatchExhaustiveBlock(arm.body, out, file)
1197
+ break
1198
+ }
1199
+ case 'if':
1200
+ checkPreferMatchExhaustiveBlock(stmt.then, out, file)
1201
+ if (stmt.else_) checkPreferMatchExhaustiveBlock(stmt.else_, out, file)
1202
+ break
1203
+ case 'while':
1204
+ checkPreferMatchExhaustiveBlock(stmt.body, out, file)
1205
+ if (stmt.step) checkPreferMatchExhaustiveBlock(stmt.step, out, file)
1206
+ break
1207
+ case 'foreach':
1208
+ checkPreferMatchExhaustiveBlock(stmt.body, out, file)
1209
+ break
1210
+ case 'execute':
1211
+ checkPreferMatchExhaustiveBlock(stmt.body, out, file)
1212
+ break
1213
+ case 'if_let_some':
1214
+ checkPreferMatchExhaustiveBlock(stmt.then, out, file)
1215
+ if (stmt.else_) checkPreferMatchExhaustiveBlock(stmt.else_, out, file)
1216
+ break
1217
+ case 'while_let_some':
1218
+ checkPreferMatchExhaustiveBlock(stmt.body, out, file)
1219
+ break
1220
+ case 'labeled_loop':
1221
+ checkPreferMatchExhaustiveStmt(stmt.body, out, file)
1222
+ break
1223
+ }
1224
+ }
1225
+
1226
+ // ---------------------------------------------------------------------------
1227
+ // Rule: no-empty-catch
1228
+ // ---------------------------------------------------------------------------
1229
+ //
1230
+ // RedScript has no try/catch. The equivalent pattern is `if_let_some` with an
1231
+ // empty else_ block (silently ignoring the None case) or a match arm whose
1232
+ // body is empty. Both patterns silently swallow a failure — warn about them.
1233
+
1234
+ function checkNoEmptyCatch(fn: HIRFunction, file?: string): LintWarning[] {
1235
+ const warnings: LintWarning[] = []
1236
+ checkNoEmptyCatchBlock(fn.body, warnings, file)
1237
+ return warnings
1238
+ }
1239
+
1240
+ function checkNoEmptyCatchBlock(block: HIRBlock, out: LintWarning[], file: string | undefined): void {
1241
+ for (const stmt of block) {
1242
+ checkNoEmptyCatchStmt(stmt, out, file)
1243
+ }
1244
+ }
1245
+
1246
+ function checkNoEmptyCatchStmt(stmt: HIRStmt, out: LintWarning[], file: string | undefined): void {
1247
+ switch (stmt.kind) {
1248
+ case 'if_let_some':
1249
+ if (stmt.else_ && stmt.else_.length === 0) {
1250
+ const warn: LintWarning = {
1251
+ rule: 'no-empty-catch',
1252
+ message: `Empty else block in if let Some — None case is silently ignored`,
1253
+ file,
1254
+ }
1255
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1256
+ out.push(warn)
1257
+ }
1258
+ checkNoEmptyCatchBlock(stmt.then, out, file)
1259
+ if (stmt.else_) checkNoEmptyCatchBlock(stmt.else_, out, file)
1260
+ break
1261
+ case 'match':
1262
+ for (const arm of stmt.arms) {
1263
+ if (arm.body.length === 0) {
1264
+ const warn: LintWarning = {
1265
+ rule: 'no-empty-catch',
1266
+ message: `Empty match arm body — consider handling this case explicitly`,
1267
+ file,
1268
+ }
1269
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1270
+ out.push(warn)
1271
+ }
1272
+ checkNoEmptyCatchBlock(arm.body, out, file)
1273
+ }
1274
+ break
1275
+ case 'if':
1276
+ checkNoEmptyCatchBlock(stmt.then, out, file)
1277
+ if (stmt.else_) checkNoEmptyCatchBlock(stmt.else_, out, file)
1278
+ break
1279
+ case 'while':
1280
+ checkNoEmptyCatchBlock(stmt.body, out, file)
1281
+ if (stmt.step) checkNoEmptyCatchBlock(stmt.step, out, file)
1282
+ break
1283
+ case 'foreach':
1284
+ checkNoEmptyCatchBlock(stmt.body, out, file)
1285
+ break
1286
+ case 'execute':
1287
+ checkNoEmptyCatchBlock(stmt.body, out, file)
1288
+ break
1289
+ case 'while_let_some':
1290
+ checkNoEmptyCatchBlock(stmt.body, out, file)
1291
+ break
1292
+ case 'labeled_loop':
1293
+ checkNoEmptyCatchStmt(stmt.body, out, file)
1294
+ break
1295
+ }
1296
+ }
1297
+
1298
+ // ---------------------------------------------------------------------------
1299
+ // Rule: naming-convention
1300
+ // ---------------------------------------------------------------------------
1301
+ //
1302
+ // Variables (let bindings, foreach bindings) must use camelCase.
1303
+ // Type names (structs, enums) must use PascalCase.
1304
+ //
1305
+ // camelCase: starts with lowercase letter, no underscores (except leading _)
1306
+ // PascalCase: starts with uppercase letter
1307
+
1308
+ function isCamelCase(name: string): boolean {
1309
+ // Allow leading underscore (private convention), then must start lowercase
1310
+ const stripped = name.startsWith('_') ? name.slice(1) : name
1311
+ if (stripped.length === 0) return true
1312
+ // Must start with lowercase, no underscores after first char
1313
+ return /^[a-z][a-zA-Z0-9]*$/.test(stripped)
1314
+ }
1315
+
1316
+ function isPascalCase(name: string): boolean {
1317
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name)
1318
+ }
1319
+
1320
+ function checkNamingConvention(fn: HIRFunction, file?: string): LintWarning[] {
1321
+ const warnings: LintWarning[] = []
1322
+ checkNamingConventionBlock(fn.body, warnings, file)
1323
+ return warnings
1324
+ }
1325
+
1326
+ function checkNamingConventionBlock(block: HIRBlock, out: LintWarning[], file: string | undefined): void {
1327
+ for (const stmt of block) {
1328
+ checkNamingConventionStmt(stmt, out, file)
1329
+ }
1330
+ }
1331
+
1332
+ function checkNamingConventionStmt(stmt: HIRStmt, out: LintWarning[], file: string | undefined): void {
1333
+ switch (stmt.kind) {
1334
+ case 'let':
1335
+ if (!isCamelCase(stmt.name)) {
1336
+ const warn: LintWarning = {
1337
+ rule: 'naming-convention',
1338
+ message: `Variable "${stmt.name}" should use camelCase`,
1339
+ file,
1340
+ }
1341
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1342
+ out.push(warn)
1343
+ }
1344
+ break
1345
+ case 'let_destruct':
1346
+ for (const name of stmt.names) {
1347
+ if (!isCamelCase(name)) {
1348
+ const warn: LintWarning = {
1349
+ rule: 'naming-convention',
1350
+ message: `Variable "${name}" should use camelCase`,
1351
+ file,
1352
+ }
1353
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1354
+ out.push(warn)
1355
+ }
1356
+ }
1357
+ break
1358
+ case 'foreach':
1359
+ if (!isCamelCase(stmt.binding)) {
1360
+ const warn: LintWarning = {
1361
+ rule: 'naming-convention',
1362
+ message: `Loop variable "${stmt.binding}" should use camelCase`,
1363
+ file,
1364
+ }
1365
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1366
+ out.push(warn)
1367
+ }
1368
+ checkNamingConventionBlock(stmt.body, out, file)
1369
+ break
1370
+ case 'if':
1371
+ checkNamingConventionBlock(stmt.then, out, file)
1372
+ if (stmt.else_) checkNamingConventionBlock(stmt.else_, out, file)
1373
+ break
1374
+ case 'while':
1375
+ checkNamingConventionBlock(stmt.body, out, file)
1376
+ if (stmt.step) checkNamingConventionBlock(stmt.step, out, file)
1377
+ break
1378
+ case 'match':
1379
+ for (const arm of stmt.arms) checkNamingConventionBlock(arm.body, out, file)
1380
+ break
1381
+ case 'execute':
1382
+ checkNamingConventionBlock(stmt.body, out, file)
1383
+ break
1384
+ case 'if_let_some':
1385
+ if (!isCamelCase(stmt.binding)) {
1386
+ const warn: LintWarning = {
1387
+ rule: 'naming-convention',
1388
+ message: `Binding "${stmt.binding}" should use camelCase`,
1389
+ file,
1390
+ }
1391
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1392
+ out.push(warn)
1393
+ }
1394
+ checkNamingConventionBlock(stmt.then, out, file)
1395
+ if (stmt.else_) checkNamingConventionBlock(stmt.else_, out, file)
1396
+ break
1397
+ case 'while_let_some':
1398
+ if (!isCamelCase(stmt.binding)) {
1399
+ const warn: LintWarning = {
1400
+ rule: 'naming-convention',
1401
+ message: `Binding "${stmt.binding}" should use camelCase`,
1402
+ file,
1403
+ }
1404
+ if (stmt.span) { warn.line = stmt.span.line; warn.col = stmt.span.col }
1405
+ out.push(warn)
1406
+ }
1407
+ checkNamingConventionBlock(stmt.body, out, file)
1408
+ break
1409
+ case 'labeled_loop':
1410
+ checkNamingConventionStmt(stmt.body, out, file)
1411
+ break
1412
+ }
1413
+ }
1414
+
1415
+ /** Check type names (structs and enums) at the module level. */
1416
+ function checkNamingConventionModule(hir: HIRModule, file?: string): LintWarning[] {
1417
+ const warnings: LintWarning[] = []
1418
+ for (const s of hir.structs) {
1419
+ if (!isPascalCase(s.name)) {
1420
+ const warn: LintWarning = {
1421
+ rule: 'naming-convention',
1422
+ message: `Struct "${s.name}" should use PascalCase`,
1423
+ file,
1424
+ }
1425
+ if (s.span) { warn.line = s.span.line; warn.col = s.span.col }
1426
+ warnings.push(warn)
1427
+ }
1428
+ }
1429
+ for (const e of hir.enums) {
1430
+ if (!isPascalCase(e.name)) {
1431
+ const warn: LintWarning = {
1432
+ rule: 'naming-convention',
1433
+ message: `Enum "${e.name}" should use PascalCase`,
1434
+ file,
1435
+ }
1436
+ if (e.span) { warn.line = e.span.line; warn.col = e.span.col }
1437
+ warnings.push(warn)
1438
+ }
1439
+ }
1440
+ return warnings
1441
+ }
1442
+
1443
+ // ---------------------------------------------------------------------------
1444
+ // Rule: no-magic-numbers
1445
+ // ---------------------------------------------------------------------------
1446
+ //
1447
+ // Flags any numeric literal that is not in the allowedNumbers list (default
1448
+ // [0, 1]) when used outside of a const declaration. Unlike the older
1449
+ // magic-number rule (threshold > 1), this rule checks against an explicit
1450
+ // allow-list, making it configurable for different projects.
1451
+
1452
+ function checkNoMagicNumbers(fn: HIRFunction, allowed: number[], file?: string): LintWarning[] {
1453
+ const warnings: LintWarning[] = []
1454
+ const allowedSet = new Set(allowed)
1455
+ checkNoMagicNumbersBlock(fn.body, warnings, file, allowedSet, /*inConst=*/false)
1456
+ return warnings
1457
+ }
1458
+
1459
+ function checkNoMagicNumbersBlock(
1460
+ block: HIRBlock,
1461
+ out: LintWarning[],
1462
+ file: string | undefined,
1463
+ allowed: Set<number>,
1464
+ inConst: boolean,
1465
+ ): void {
1466
+ for (const stmt of block) {
1467
+ checkNoMagicNumbersStmt(stmt, out, file, allowed, inConst)
1468
+ }
1469
+ }
1470
+
1471
+ function checkNoMagicNumbersStmt(
1472
+ stmt: HIRStmt,
1473
+ out: LintWarning[],
1474
+ file: string | undefined,
1475
+ allowed: Set<number>,
1476
+ inConst: boolean,
1477
+ ): void {
1478
+ switch (stmt.kind) {
1479
+ case 'const_decl':
1480
+ // Numbers in const declarations are the intended fix — skip
1481
+ break
1482
+ case 'let':
1483
+ checkNoMagicNumbersExpr(stmt.init, out, file, allowed)
1484
+ break
1485
+ case 'let_destruct':
1486
+ checkNoMagicNumbersExpr(stmt.init, out, file, allowed)
1487
+ break
1488
+ case 'expr':
1489
+ checkNoMagicNumbersExpr(stmt.expr, out, file, allowed)
1490
+ break
1491
+ case 'return':
1492
+ if (stmt.value) checkNoMagicNumbersExpr(stmt.value, out, file, allowed)
1493
+ break
1494
+ case 'if':
1495
+ checkNoMagicNumbersExpr(stmt.cond, out, file, allowed)
1496
+ checkNoMagicNumbersBlock(stmt.then, out, file, allowed, false)
1497
+ if (stmt.else_) checkNoMagicNumbersBlock(stmt.else_, out, file, allowed, false)
1498
+ break
1499
+ case 'while':
1500
+ checkNoMagicNumbersExpr(stmt.cond, out, file, allowed)
1501
+ checkNoMagicNumbersBlock(stmt.body, out, file, allowed, false)
1502
+ if (stmt.step) checkNoMagicNumbersBlock(stmt.step, out, file, allowed, false)
1503
+ break
1504
+ case 'foreach':
1505
+ checkNoMagicNumbersExpr(stmt.iterable, out, file, allowed)
1506
+ checkNoMagicNumbersBlock(stmt.body, out, file, allowed, false)
1507
+ break
1508
+ case 'match':
1509
+ checkNoMagicNumbersExpr(stmt.expr, out, file, allowed)
1510
+ for (const arm of stmt.arms) checkNoMagicNumbersBlock(arm.body, out, file, allowed, false)
1511
+ break
1512
+ case 'execute':
1513
+ checkNoMagicNumbersBlock(stmt.body, out, file, allowed, false)
1514
+ break
1515
+ case 'if_let_some':
1516
+ checkNoMagicNumbersExpr(stmt.init, out, file, allowed)
1517
+ checkNoMagicNumbersBlock(stmt.then, out, file, allowed, false)
1518
+ if (stmt.else_) checkNoMagicNumbersBlock(stmt.else_, out, file, allowed, false)
1519
+ break
1520
+ case 'while_let_some':
1521
+ checkNoMagicNumbersExpr(stmt.init, out, file, allowed)
1522
+ checkNoMagicNumbersBlock(stmt.body, out, file, allowed, false)
1523
+ break
1524
+ case 'labeled_loop':
1525
+ checkNoMagicNumbersStmt(stmt.body, out, file, allowed, inConst)
1526
+ break
1527
+ }
1528
+ }
1529
+
1530
+ function checkNoMagicNumbersExpr(
1531
+ expr: HIRExpr | undefined,
1532
+ out: LintWarning[],
1533
+ file: string | undefined,
1534
+ allowed: Set<number>,
1535
+ ): void {
1536
+ if (!expr) return
1537
+ switch (expr.kind) {
1538
+ case 'int_lit':
1539
+ case 'float_lit':
1540
+ case 'byte_lit':
1541
+ case 'short_lit':
1542
+ case 'long_lit':
1543
+ case 'double_lit':
1544
+ if (!allowed.has(expr.value)) {
1545
+ const warn: LintWarning = {
1546
+ rule: 'no-magic-numbers',
1547
+ message: `Magic number ${expr.value} — extract to a named const`,
1548
+ file,
1549
+ }
1550
+ if (expr.span) { warn.line = expr.span.line; warn.col = expr.span.col }
1551
+ out.push(warn)
1552
+ }
1553
+ break
1554
+ case 'binary':
1555
+ checkNoMagicNumbersExpr(expr.left, out, file, allowed)
1556
+ checkNoMagicNumbersExpr(expr.right, out, file, allowed)
1557
+ break
1558
+ case 'unary':
1559
+ checkNoMagicNumbersExpr(expr.operand, out, file, allowed)
1560
+ break
1561
+ case 'call':
1562
+ for (const arg of expr.args) checkNoMagicNumbersExpr(arg, out, file, allowed)
1563
+ break
1564
+ case 'invoke':
1565
+ checkNoMagicNumbersExpr(expr.callee, out, file, allowed)
1566
+ for (const arg of expr.args) checkNoMagicNumbersExpr(arg, out, file, allowed)
1567
+ break
1568
+ case 'static_call':
1569
+ for (const arg of expr.args) checkNoMagicNumbersExpr(arg, out, file, allowed)
1570
+ break
1571
+ case 'member':
1572
+ checkNoMagicNumbersExpr(expr.obj, out, file, allowed)
1573
+ break
1574
+ case 'member_assign':
1575
+ checkNoMagicNumbersExpr(expr.obj, out, file, allowed)
1576
+ checkNoMagicNumbersExpr(expr.value, out, file, allowed)
1577
+ break
1578
+ case 'index':
1579
+ checkNoMagicNumbersExpr(expr.obj, out, file, allowed)
1580
+ checkNoMagicNumbersExpr(expr.index, out, file, allowed)
1581
+ break
1582
+ case 'index_assign':
1583
+ checkNoMagicNumbersExpr(expr.obj, out, file, allowed)
1584
+ checkNoMagicNumbersExpr(expr.index, out, file, allowed)
1585
+ checkNoMagicNumbersExpr(expr.value, out, file, allowed)
1586
+ break
1587
+ case 'assign':
1588
+ checkNoMagicNumbersExpr(expr.value, out, file, allowed)
1589
+ break
1590
+ case 'some_lit':
1591
+ checkNoMagicNumbersExpr(expr.value, out, file, allowed)
1592
+ break
1593
+ case 'unwrap_or':
1594
+ checkNoMagicNumbersExpr(expr.opt, out, file, allowed)
1595
+ checkNoMagicNumbersExpr(expr.default_, out, file, allowed)
1596
+ break
1597
+ case 'type_cast':
1598
+ checkNoMagicNumbersExpr(expr.expr, out, file, allowed)
1599
+ break
1600
+ case 'array_lit':
1601
+ case 'tuple_lit':
1602
+ for (const e of expr.elements) checkNoMagicNumbersExpr(e, out, file, allowed)
1603
+ break
1604
+ case 'struct_lit':
1605
+ for (const f of expr.fields) checkNoMagicNumbersExpr(f.value, out, file, allowed)
1606
+ break
1607
+ case 'str_interp':
1608
+ for (const p of expr.parts) {
1609
+ if (typeof p !== 'string') checkNoMagicNumbersExpr(p, out, file, allowed)
1610
+ }
1611
+ break
1612
+ case 'f_string':
1613
+ for (const p of expr.parts) {
1614
+ if (p.kind === 'expr') checkNoMagicNumbersExpr(p.expr, out, file, allowed)
1615
+ }
1616
+ break
1617
+ case 'lambda':
1618
+ if (Array.isArray(expr.body)) {
1619
+ checkNoMagicNumbersBlock(expr.body as HIRBlock, out, file, allowed, false)
1620
+ } else {
1621
+ checkNoMagicNumbersExpr(expr.body as HIRExpr, out, file, allowed)
1622
+ }
1623
+ break
1624
+ case 'enum_construct':
1625
+ for (const f of expr.args) checkNoMagicNumbersExpr(f.value, out, file, allowed)
1626
+ break
1627
+ default:
1628
+ break
1629
+ }
1630
+ }