kimaki 0.4.82 → 0.4.84

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 (264) hide show
  1. package/LICENSE +21 -0
  2. package/dist/anthropic-auth-plugin.js +7 -0
  3. package/dist/cli.js +51 -7
  4. package/dist/commands/abort.js +5 -16
  5. package/dist/commands/action-buttons.js +3 -3
  6. package/dist/commands/add-project.js +1 -1
  7. package/dist/commands/ask-question.js +3 -3
  8. package/dist/commands/context-usage.js +1 -1
  9. package/dist/commands/create-new-project.js +1 -1
  10. package/dist/commands/fork.js +11 -8
  11. package/dist/commands/merge-worktree.js +1 -1
  12. package/dist/commands/new-worktree.js +63 -44
  13. package/dist/commands/remove-project.js +1 -1
  14. package/dist/commands/resume.js +11 -8
  15. package/dist/commands/screenshare.js +14 -6
  16. package/dist/commands/screenshare.test.js +20 -0
  17. package/dist/commands/session.js +1 -1
  18. package/dist/commands/undo-redo.js +91 -7
  19. package/dist/commands/user-command.js +1 -1
  20. package/dist/config.js +16 -1
  21. package/dist/database.js +53 -2
  22. package/dist/db.js +6 -0
  23. package/dist/discord-bot.js +48 -85
  24. package/dist/discord-command-registration.js +1 -1
  25. package/dist/external-opencode-sync.js +515 -0
  26. package/dist/external-opencode-sync.test.js +151 -0
  27. package/dist/gateway-proxy.e2e.test.js +8 -5
  28. package/dist/genai.js +1 -1
  29. package/dist/generated/enums.js +4 -0
  30. package/dist/generated/internal/class.js +4 -4
  31. package/dist/generated/internal/prismaNamespace.js +1 -0
  32. package/dist/generated/internal/prismaNamespaceBrowser.js +1 -0
  33. package/dist/generated/models/external_session_pending_prompts.js +1 -0
  34. package/dist/hrana-server.js +14 -285
  35. package/dist/hrana-server.test.js +4 -2
  36. package/dist/kimaki-opencode-plugin-loading.e2e.test.js +7 -0
  37. package/dist/kimaki-opencode-plugin.js +2 -0
  38. package/dist/kitty-graphics-parser.js +3 -0
  39. package/dist/kitty-graphics-parser.test.js +276 -0
  40. package/dist/kitty-graphics-plugin.js +3 -0
  41. package/dist/markdown.js +4 -4
  42. package/dist/markdown.test.js +1 -1
  43. package/dist/message-formatting.js +54 -15
  44. package/dist/onboarding-tutorial.js +1 -1
  45. package/dist/openai-realtime.js +9 -13
  46. package/dist/opencode.js +28 -5
  47. package/dist/queue-advanced-e2e-setup.js +89 -0
  48. package/dist/queue-advanced-permissions-typing.e2e.test.js +5 -5
  49. package/dist/queue-advanced-typing.e2e.test.js +9 -22
  50. package/dist/queue-question-select-drain.e2e.test.js +117 -0
  51. package/dist/session-handler/event-stream-state.js +101 -7
  52. package/dist/session-handler/event-stream-state.test.js +7 -3
  53. package/dist/session-handler/thread-session-runtime.js +120 -9
  54. package/dist/store.js +1 -0
  55. package/dist/system-message.js +22 -4
  56. package/dist/system-message.test.js +19 -0
  57. package/dist/task-runner.js +1 -1
  58. package/dist/thread-message-queue.e2e.test.js +8 -14
  59. package/dist/tools.js +1 -1
  60. package/dist/undo-redo.e2e.test.js +20 -25
  61. package/package.json +10 -6
  62. package/schema.prisma +6 -0
  63. package/skills/errore/SKILL.md +40 -13
  64. package/skills/goke/SKILL.md +12 -0
  65. package/skills/lintcn/SKILL.md +868 -0
  66. package/skills/npm-package/SKILL.md +1 -0
  67. package/skills/proxyman/SKILL.md +215 -0
  68. package/skills/spiceflow/SKILL.md +1 -1
  69. package/skills/usecomputer/SKILL.md +339 -0
  70. package/src/ai-tool-to-genai.ts +1 -0
  71. package/src/anthropic-auth-plugin.ts +7 -0
  72. package/src/cli.ts +59 -6
  73. package/src/commands/abort.ts +6 -16
  74. package/src/commands/action-buttons.ts +5 -1
  75. package/src/commands/add-project.ts +1 -1
  76. package/src/commands/ask-question.ts +5 -2
  77. package/src/commands/context-usage.ts +1 -1
  78. package/src/commands/create-new-project.ts +1 -1
  79. package/src/commands/fork.ts +12 -11
  80. package/src/commands/merge-worktree.ts +1 -1
  81. package/src/commands/new-worktree.ts +74 -55
  82. package/src/commands/remove-project.ts +1 -1
  83. package/src/commands/resume.ts +12 -10
  84. package/src/commands/screenshare.test.ts +30 -0
  85. package/src/commands/screenshare.ts +18 -6
  86. package/src/commands/session.ts +1 -1
  87. package/src/commands/undo-redo.ts +108 -10
  88. package/src/commands/user-command.ts +1 -1
  89. package/src/config.ts +19 -1
  90. package/src/database.ts +72 -3
  91. package/src/db.ts +8 -0
  92. package/src/discord-bot.ts +58 -93
  93. package/src/discord-command-registration.ts +1 -1
  94. package/src/external-opencode-sync.ts +729 -0
  95. package/src/gateway-proxy.e2e.test.ts +9 -5
  96. package/src/genai.ts +3 -3
  97. package/src/generated/commonInputTypes.ts +34 -0
  98. package/src/generated/enums.ts +8 -0
  99. package/src/generated/internal/class.ts +4 -4
  100. package/src/generated/internal/prismaNamespace.ts +8 -0
  101. package/src/generated/internal/prismaNamespaceBrowser.ts +1 -0
  102. package/src/generated/models/thread_sessions.ts +53 -1
  103. package/src/hrana-server.test.ts +8 -2
  104. package/src/hrana-server.ts +18 -390
  105. package/src/kimaki-opencode-plugin-loading.e2e.test.ts +7 -0
  106. package/src/kimaki-opencode-plugin.ts +2 -0
  107. package/src/markdown.test.ts +1 -1
  108. package/src/markdown.ts +4 -4
  109. package/src/message-formatting.ts +66 -17
  110. package/src/onboarding-tutorial.ts +1 -1
  111. package/src/openai-realtime.ts +6 -10
  112. package/src/opencode.ts +31 -7
  113. package/src/queue-advanced-e2e-setup.ts +92 -0
  114. package/src/queue-advanced-permissions-typing.e2e.test.ts +5 -5
  115. package/src/queue-advanced-typing.e2e.test.ts +9 -22
  116. package/src/queue-question-select-drain.e2e.test.ts +149 -0
  117. package/src/schema.sql +1 -0
  118. package/src/session-handler/event-stream-state.test.ts +7 -2
  119. package/src/session-handler/event-stream-state.ts +128 -7
  120. package/src/session-handler/thread-runtime-state.ts +5 -0
  121. package/src/session-handler/thread-session-runtime.ts +153 -11
  122. package/src/store.ts +8 -0
  123. package/src/system-message.ts +27 -4
  124. package/src/task-runner.ts +1 -1
  125. package/src/thread-message-queue.e2e.test.ts +8 -14
  126. package/src/tools.ts +1 -1
  127. package/src/undo-redo.e2e.test.ts +28 -26
  128. package/skills/jitter/node_modules/.bin/esbuild +0 -21
  129. package/skills/jitter/node_modules/.bin/tsc +0 -21
  130. package/skills/jitter/node_modules/.bin/tsserver +0 -21
  131. package/skills/jitter/node_modules/typescript/LICENSE.txt +0 -55
  132. package/skills/jitter/node_modules/typescript/README.md +0 -50
  133. package/skills/jitter/node_modules/typescript/SECURITY.md +0 -41
  134. package/skills/jitter/node_modules/typescript/ThirdPartyNoticeText.txt +0 -193
  135. package/skills/jitter/node_modules/typescript/bin/tsc +0 -2
  136. package/skills/jitter/node_modules/typescript/bin/tsserver +0 -2
  137. package/skills/jitter/node_modules/typescript/lib/_tsc.js +0 -133792
  138. package/skills/jitter/node_modules/typescript/lib/_tsserver.js +0 -659
  139. package/skills/jitter/node_modules/typescript/lib/_typingsInstaller.js +0 -222
  140. package/skills/jitter/node_modules/typescript/lib/cs/diagnosticMessages.generated.json +0 -2122
  141. package/skills/jitter/node_modules/typescript/lib/de/diagnosticMessages.generated.json +0 -2122
  142. package/skills/jitter/node_modules/typescript/lib/es/diagnosticMessages.generated.json +0 -2122
  143. package/skills/jitter/node_modules/typescript/lib/fr/diagnosticMessages.generated.json +0 -2122
  144. package/skills/jitter/node_modules/typescript/lib/it/diagnosticMessages.generated.json +0 -2122
  145. package/skills/jitter/node_modules/typescript/lib/ja/diagnosticMessages.generated.json +0 -2122
  146. package/skills/jitter/node_modules/typescript/lib/ko/diagnosticMessages.generated.json +0 -2122
  147. package/skills/jitter/node_modules/typescript/lib/lib.d.ts +0 -22
  148. package/skills/jitter/node_modules/typescript/lib/lib.decorators.d.ts +0 -384
  149. package/skills/jitter/node_modules/typescript/lib/lib.decorators.legacy.d.ts +0 -22
  150. package/skills/jitter/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +0 -41
  151. package/skills/jitter/node_modules/typescript/lib/lib.dom.d.ts +0 -39429
  152. package/skills/jitter/node_modules/typescript/lib/lib.dom.iterable.d.ts +0 -571
  153. package/skills/jitter/node_modules/typescript/lib/lib.es2015.collection.d.ts +0 -147
  154. package/skills/jitter/node_modules/typescript/lib/lib.es2015.core.d.ts +0 -597
  155. package/skills/jitter/node_modules/typescript/lib/lib.es2015.d.ts +0 -28
  156. package/skills/jitter/node_modules/typescript/lib/lib.es2015.generator.d.ts +0 -77
  157. package/skills/jitter/node_modules/typescript/lib/lib.es2015.iterable.d.ts +0 -605
  158. package/skills/jitter/node_modules/typescript/lib/lib.es2015.promise.d.ts +0 -81
  159. package/skills/jitter/node_modules/typescript/lib/lib.es2015.proxy.d.ts +0 -128
  160. package/skills/jitter/node_modules/typescript/lib/lib.es2015.reflect.d.ts +0 -144
  161. package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.d.ts +0 -46
  162. package/skills/jitter/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +0 -326
  163. package/skills/jitter/node_modules/typescript/lib/lib.es2016.array.include.d.ts +0 -116
  164. package/skills/jitter/node_modules/typescript/lib/lib.es2016.d.ts +0 -21
  165. package/skills/jitter/node_modules/typescript/lib/lib.es2016.full.d.ts +0 -23
  166. package/skills/jitter/node_modules/typescript/lib/lib.es2016.intl.d.ts +0 -31
  167. package/skills/jitter/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +0 -21
  168. package/skills/jitter/node_modules/typescript/lib/lib.es2017.d.ts +0 -26
  169. package/skills/jitter/node_modules/typescript/lib/lib.es2017.date.d.ts +0 -31
  170. package/skills/jitter/node_modules/typescript/lib/lib.es2017.full.d.ts +0 -23
  171. package/skills/jitter/node_modules/typescript/lib/lib.es2017.intl.d.ts +0 -44
  172. package/skills/jitter/node_modules/typescript/lib/lib.es2017.object.d.ts +0 -49
  173. package/skills/jitter/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +0 -135
  174. package/skills/jitter/node_modules/typescript/lib/lib.es2017.string.d.ts +0 -45
  175. package/skills/jitter/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +0 -53
  176. package/skills/jitter/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +0 -77
  177. package/skills/jitter/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +0 -53
  178. package/skills/jitter/node_modules/typescript/lib/lib.es2018.d.ts +0 -24
  179. package/skills/jitter/node_modules/typescript/lib/lib.es2018.full.d.ts +0 -24
  180. package/skills/jitter/node_modules/typescript/lib/lib.es2018.intl.d.ts +0 -83
  181. package/skills/jitter/node_modules/typescript/lib/lib.es2018.promise.d.ts +0 -30
  182. package/skills/jitter/node_modules/typescript/lib/lib.es2018.regexp.d.ts +0 -37
  183. package/skills/jitter/node_modules/typescript/lib/lib.es2019.array.d.ts +0 -79
  184. package/skills/jitter/node_modules/typescript/lib/lib.es2019.d.ts +0 -24
  185. package/skills/jitter/node_modules/typescript/lib/lib.es2019.full.d.ts +0 -24
  186. package/skills/jitter/node_modules/typescript/lib/lib.es2019.intl.d.ts +0 -23
  187. package/skills/jitter/node_modules/typescript/lib/lib.es2019.object.d.ts +0 -33
  188. package/skills/jitter/node_modules/typescript/lib/lib.es2019.string.d.ts +0 -37
  189. package/skills/jitter/node_modules/typescript/lib/lib.es2019.symbol.d.ts +0 -24
  190. package/skills/jitter/node_modules/typescript/lib/lib.es2020.bigint.d.ts +0 -765
  191. package/skills/jitter/node_modules/typescript/lib/lib.es2020.d.ts +0 -27
  192. package/skills/jitter/node_modules/typescript/lib/lib.es2020.date.d.ts +0 -42
  193. package/skills/jitter/node_modules/typescript/lib/lib.es2020.full.d.ts +0 -24
  194. package/skills/jitter/node_modules/typescript/lib/lib.es2020.intl.d.ts +0 -474
  195. package/skills/jitter/node_modules/typescript/lib/lib.es2020.number.d.ts +0 -28
  196. package/skills/jitter/node_modules/typescript/lib/lib.es2020.promise.d.ts +0 -47
  197. package/skills/jitter/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +0 -99
  198. package/skills/jitter/node_modules/typescript/lib/lib.es2020.string.d.ts +0 -44
  199. package/skills/jitter/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +0 -41
  200. package/skills/jitter/node_modules/typescript/lib/lib.es2021.d.ts +0 -23
  201. package/skills/jitter/node_modules/typescript/lib/lib.es2021.full.d.ts +0 -24
  202. package/skills/jitter/node_modules/typescript/lib/lib.es2021.intl.d.ts +0 -166
  203. package/skills/jitter/node_modules/typescript/lib/lib.es2021.promise.d.ts +0 -48
  204. package/skills/jitter/node_modules/typescript/lib/lib.es2021.string.d.ts +0 -33
  205. package/skills/jitter/node_modules/typescript/lib/lib.es2021.weakref.d.ts +0 -78
  206. package/skills/jitter/node_modules/typescript/lib/lib.es2022.array.d.ts +0 -121
  207. package/skills/jitter/node_modules/typescript/lib/lib.es2022.d.ts +0 -25
  208. package/skills/jitter/node_modules/typescript/lib/lib.es2022.error.d.ts +0 -75
  209. package/skills/jitter/node_modules/typescript/lib/lib.es2022.full.d.ts +0 -24
  210. package/skills/jitter/node_modules/typescript/lib/lib.es2022.intl.d.ts +0 -145
  211. package/skills/jitter/node_modules/typescript/lib/lib.es2022.object.d.ts +0 -26
  212. package/skills/jitter/node_modules/typescript/lib/lib.es2022.regexp.d.ts +0 -39
  213. package/skills/jitter/node_modules/typescript/lib/lib.es2022.string.d.ts +0 -25
  214. package/skills/jitter/node_modules/typescript/lib/lib.es2023.array.d.ts +0 -924
  215. package/skills/jitter/node_modules/typescript/lib/lib.es2023.collection.d.ts +0 -21
  216. package/skills/jitter/node_modules/typescript/lib/lib.es2023.d.ts +0 -22
  217. package/skills/jitter/node_modules/typescript/lib/lib.es2023.full.d.ts +0 -24
  218. package/skills/jitter/node_modules/typescript/lib/lib.es2023.intl.d.ts +0 -56
  219. package/skills/jitter/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +0 -65
  220. package/skills/jitter/node_modules/typescript/lib/lib.es2024.collection.d.ts +0 -29
  221. package/skills/jitter/node_modules/typescript/lib/lib.es2024.d.ts +0 -26
  222. package/skills/jitter/node_modules/typescript/lib/lib.es2024.full.d.ts +0 -24
  223. package/skills/jitter/node_modules/typescript/lib/lib.es2024.object.d.ts +0 -29
  224. package/skills/jitter/node_modules/typescript/lib/lib.es2024.promise.d.ts +0 -35
  225. package/skills/jitter/node_modules/typescript/lib/lib.es2024.regexp.d.ts +0 -25
  226. package/skills/jitter/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +0 -68
  227. package/skills/jitter/node_modules/typescript/lib/lib.es2024.string.d.ts +0 -29
  228. package/skills/jitter/node_modules/typescript/lib/lib.es5.d.ts +0 -4601
  229. package/skills/jitter/node_modules/typescript/lib/lib.es6.d.ts +0 -23
  230. package/skills/jitter/node_modules/typescript/lib/lib.esnext.array.d.ts +0 -35
  231. package/skills/jitter/node_modules/typescript/lib/lib.esnext.collection.d.ts +0 -96
  232. package/skills/jitter/node_modules/typescript/lib/lib.esnext.d.ts +0 -29
  233. package/skills/jitter/node_modules/typescript/lib/lib.esnext.decorators.d.ts +0 -28
  234. package/skills/jitter/node_modules/typescript/lib/lib.esnext.disposable.d.ts +0 -193
  235. package/skills/jitter/node_modules/typescript/lib/lib.esnext.error.d.ts +0 -24
  236. package/skills/jitter/node_modules/typescript/lib/lib.esnext.float16.d.ts +0 -443
  237. package/skills/jitter/node_modules/typescript/lib/lib.esnext.full.d.ts +0 -24
  238. package/skills/jitter/node_modules/typescript/lib/lib.esnext.intl.d.ts +0 -21
  239. package/skills/jitter/node_modules/typescript/lib/lib.esnext.iterator.d.ts +0 -148
  240. package/skills/jitter/node_modules/typescript/lib/lib.esnext.promise.d.ts +0 -34
  241. package/skills/jitter/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +0 -25
  242. package/skills/jitter/node_modules/typescript/lib/lib.scripthost.d.ts +0 -322
  243. package/skills/jitter/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +0 -41
  244. package/skills/jitter/node_modules/typescript/lib/lib.webworker.d.ts +0 -13150
  245. package/skills/jitter/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +0 -23
  246. package/skills/jitter/node_modules/typescript/lib/lib.webworker.iterable.d.ts +0 -340
  247. package/skills/jitter/node_modules/typescript/lib/pl/diagnosticMessages.generated.json +0 -2122
  248. package/skills/jitter/node_modules/typescript/lib/pt-br/diagnosticMessages.generated.json +0 -2122
  249. package/skills/jitter/node_modules/typescript/lib/ru/diagnosticMessages.generated.json +0 -2122
  250. package/skills/jitter/node_modules/typescript/lib/tr/diagnosticMessages.generated.json +0 -2122
  251. package/skills/jitter/node_modules/typescript/lib/tsc.js +0 -8
  252. package/skills/jitter/node_modules/typescript/lib/tsserver.js +0 -8
  253. package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.d.ts +0 -17
  254. package/skills/jitter/node_modules/typescript/lib/tsserverlibrary.js +0 -21
  255. package/skills/jitter/node_modules/typescript/lib/typesMap.json +0 -497
  256. package/skills/jitter/node_modules/typescript/lib/typescript.d.ts +0 -11438
  257. package/skills/jitter/node_modules/typescript/lib/typescript.js +0 -200253
  258. package/skills/jitter/node_modules/typescript/lib/typingsInstaller.js +0 -8
  259. package/skills/jitter/node_modules/typescript/lib/watchGuard.js +0 -53
  260. package/skills/jitter/node_modules/typescript/lib/zh-cn/diagnosticMessages.generated.json +0 -2122
  261. package/skills/jitter/node_modules/typescript/lib/zh-tw/diagnosticMessages.generated.json +0 -2122
  262. package/skills/jitter/node_modules/typescript/node_modules/.bin/tsc +0 -21
  263. package/skills/jitter/node_modules/typescript/node_modules/.bin/tsserver +0 -21
  264. package/skills/jitter/node_modules/typescript/package.json +0 -120
@@ -0,0 +1,868 @@
1
+ ---
2
+ name: lintcn
3
+ description: "Write, add, and update type-aware TypeScript lint rules in .lintcn/ Go files. ALWAYS use this skill when creating, editing, or debugging .lintcn/*.go rule files. Covers the tsgolint rule API, AST visitors, type checker, reporting, fixes, testing, and all patterns from the 50+ built-in rules."
4
+ ---
5
+
6
+ # lintcn — Writing Custom tsgolint Lint Rules
7
+
8
+ tsgolint rules are Go functions that listen for TypeScript AST nodes and use the
9
+ TypeScript type checker for type-aware analysis. Each rule lives in its own
10
+ subfolder under `.lintcn/` and is compiled into a custom tsgolint binary.
11
+
12
+ **Every rule MUST be in a subfolder** — flat `.go` files in `.lintcn/` root are
13
+ not supported. The subfolder name = Go package name = rule identity.
14
+
15
+ Always run `go build ./...` inside `.lintcn/` to validate rules compile.
16
+ Always run `go test -v ./...` inside `.lintcn/` to run tests.
17
+
18
+ ## Directory Layout
19
+
20
+ Each rule is a subfolder. The Go package name must match the folder name:
21
+
22
+ ```
23
+ .lintcn/
24
+ no_floating_promises/
25
+ no_floating_promises.go ← rule source (committed)
26
+ no_floating_promises_test.go ← tests (committed)
27
+ options.go ← rule options struct
28
+ await_thenable/
29
+ await_thenable.go
30
+ await_thenable_test.go
31
+ my_custom_rule/
32
+ my_custom_rule.go
33
+ .gitignore ← ignores generated Go files
34
+ go.mod ← generated
35
+ go.work ← generated
36
+ .tsgolint/ ← symlink to cached source (gitignored)
37
+ ```
38
+
39
+ ## Adding Rules
40
+
41
+ ```bash
42
+ # Add a rule folder from tsgolint
43
+ npx lintcn add https://github.com/oxc-project/tsgolint/tree/main/internal/rules/no_floating_promises
44
+
45
+ # Add by file URL (auto-fetches the whole folder)
46
+ npx lintcn add https://github.com/oxc-project/tsgolint/blob/main/internal/rules/await_thenable/await_thenable.go
47
+
48
+ # List installed rules
49
+ npx lintcn list
50
+
51
+ # Remove a rule (deletes the whole subfolder)
52
+ npx lintcn remove no-floating-promises
53
+
54
+ # Lint your project
55
+ npx lintcn lint
56
+ ```
57
+
58
+ ## Rule Anatomy
59
+
60
+ Every rule is a `rule.Rule` struct with a `Name` and a `Run` function.
61
+ `Run` receives a `RuleContext` and returns a `RuleListeners` map — a map from
62
+ `ast.Kind` to callback functions. The linter walks the AST and calls your
63
+ callback when it encounters a node of that kind.
64
+
65
+ ```go
66
+ // .lintcn/my_rule/my_rule.go
67
+ package my_rule
68
+
69
+ import (
70
+ "github.com/microsoft/typescript-go/shim/ast"
71
+ "github.com/typescript-eslint/tsgolint/internal/rule"
72
+ )
73
+
74
+ var MyRule = rule.Rule{
75
+ Name: "my-rule",
76
+ Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
77
+ return rule.RuleListeners{
78
+ ast.KindCallExpression: func(node *ast.Node) {
79
+ call := node.AsCallExpression()
80
+ // analyze the call...
81
+ ctx.ReportNode(node, rule.RuleMessage{
82
+ Id: "myError",
83
+ Description: "Something is wrong here.",
84
+ })
85
+ },
86
+ }
87
+ },
88
+ }
89
+ ```
90
+
91
+ ### Metadata Comments
92
+
93
+ Add `// lintcn:` comments at the top for CLI metadata:
94
+
95
+ ```go
96
+ // lintcn:name my-rule
97
+ // lintcn:severity warn
98
+ // lintcn:description Disallow doing X without checking Y
99
+ ```
100
+
101
+ Available directives:
102
+
103
+ | Directive | Values | Default | Description |
104
+ | -------------------- | --------------- | ----------- | -------------------- |
105
+ | `lintcn:name` | kebab-case | folder name | Rule display name |
106
+ | `lintcn:severity` | `error`, `warn` | `error` | Severity level |
107
+ | `lintcn:description` | text | empty | One-line description |
108
+ | `lintcn:source` | URL | empty | Original source URL |
109
+
110
+ ### Warning Severity
111
+
112
+ Rules with `// lintcn:severity warn`:
113
+
114
+ - Don't fail CI (exit code 0)
115
+ - Only show for git-changed/untracked files — unchanged files are skipped
116
+ - Use `--all-warnings` to see warnings across the whole codebase
117
+
118
+ Warnings are for rules that guide agents writing new code without flooding
119
+ the output with violations from the rest of the codebase. Examples:
120
+
121
+ - "Remove `as any`, the actual type is `string`"
122
+ - "This `||` fallback is unreachable, the left side is never nullish"
123
+ - "Unhandled Error return value, assign to a variable and check it"
124
+
125
+ ### Package Name
126
+
127
+ Each rule subfolder has its own Go package. The package name must match the
128
+ folder name (e.g. `package no_floating_promises` in folder `no_floating_promises/`).
129
+ The exported variable name must match the pattern `var XxxRule = rule.Rule{...}`.
130
+
131
+ ## RuleContext
132
+
133
+ `ctx rule.RuleContext` provides:
134
+
135
+ | Field | Type | Description |
136
+ | --------------------------- | -------------------------- | -------------------------- |
137
+ | `SourceFile` | `*ast.SourceFile` | Current file being linted |
138
+ | `Program` | `*compiler.Program` | Full TypeScript program |
139
+ | `TypeChecker` | `*checker.Checker` | TypeScript type checker |
140
+ | `ReportNode` | `func(node, msg)` | Report error on a node |
141
+ | `ReportNodeWithFixes` | `func(node, msg, fixesFn)` | Report with auto-fixes |
142
+ | `ReportNodeWithSuggestions` | `func(node, msg, suggFn)` | Report with suggestions |
143
+ | `ReportRange` | `func(range, msg)` | Report on a text range |
144
+ | `ReportDiagnostic` | `func(diagnostic)` | Report with labeled ranges |
145
+
146
+ ## AST Node Listeners
147
+
148
+ ### Most Useful ast.Kind Values
149
+
150
+ ```go
151
+ // Statements
152
+ ast.KindExpressionStatement // bare expression: `foo();`
153
+ ast.KindReturnStatement // `return x`
154
+ ast.KindThrowStatement // `throw x`
155
+ ast.KindIfStatement // `if (x) { ... }`
156
+ ast.KindVariableDeclaration // `const x = ...`
157
+ ast.KindForInStatement // `for (x in y)`
158
+
159
+ // Expressions
160
+ ast.KindCallExpression // `foo()` — most commonly listened
161
+ ast.KindNewExpression // `new Foo()`
162
+ ast.KindBinaryExpression // `a + b`, `a === b`, `a = b`
163
+ ast.KindPropertyAccessExpression // `obj.prop`
164
+ ast.KindElementAccessExpression // `obj[key]`
165
+ ast.KindAwaitExpression // `await x`
166
+ ast.KindConditionalExpression // `a ? b : c`
167
+ ast.KindPrefixUnaryExpression // `!x`, `-x`, `typeof x`
168
+ ast.KindTemplateExpression // `hello ${name}`
169
+ ast.KindDeleteExpression // `delete obj.x`
170
+ ast.KindVoidExpression // `void x`
171
+
172
+ // Declarations
173
+ ast.KindFunctionDeclaration
174
+ ast.KindArrowFunction
175
+ ast.KindMethodDeclaration
176
+ ast.KindClassDeclaration
177
+ ast.KindEnumDeclaration
178
+
179
+ // Types
180
+ ast.KindUnionType // `A | B`
181
+ ast.KindIntersectionType // `A & B`
182
+ ast.KindAsExpression // `x as T`
183
+ ```
184
+
185
+ ### Enter and Exit Listeners
186
+
187
+ By default, listeners fire when the AST walker **enters** a node.
188
+ Use `rule.ListenerOnExit(kind)` to fire when the walker **exits** — useful
189
+ for scope tracking:
190
+
191
+ ```go
192
+ return rule.RuleListeners{
193
+ // enter function — push scope
194
+ ast.KindFunctionDeclaration: func(node *ast.Node) {
195
+ currentScope = &scopeInfo{upper: currentScope}
196
+ },
197
+ // exit function — pop scope and check
198
+ rule.ListenerOnExit(ast.KindFunctionDeclaration): func(node *ast.Node) {
199
+ if !currentScope.hasAwait {
200
+ ctx.ReportNode(node, msg)
201
+ }
202
+ currentScope = currentScope.upper
203
+ },
204
+ }
205
+ ```
206
+
207
+ Used by require_await, return_await, consistent_return, prefer_readonly for
208
+ tracking state across function bodies with a scope stack.
209
+
210
+ ### Allow/NotAllow Pattern Listeners
211
+
212
+ For destructuring and assignment contexts:
213
+
214
+ ```go
215
+ rule.ListenerOnAllowPattern(ast.KindObjectLiteralExpression) // inside destructuring
216
+ rule.ListenerOnNotAllowPattern(ast.KindArrayLiteralExpression) // outside destructuring
217
+ ```
218
+
219
+ Used by no_unsafe_assignment and unbound_method.
220
+
221
+ ## Type Checker APIs
222
+
223
+ ### Getting Types
224
+
225
+ ```go
226
+ // Get the type of any AST node
227
+ t := ctx.TypeChecker.GetTypeAtLocation(node)
228
+
229
+ // Get type with constraint resolution (unwraps type params)
230
+ t := utils.GetConstrainedTypeAtLocation(ctx.TypeChecker, node)
231
+
232
+ // Get the contextual type (what TypeScript expects at this position)
233
+ t := checker.Checker_getContextualType(ctx.TypeChecker, node, checker.ContextFlagsNone)
234
+
235
+ // Get the apparent type (resolves mapped types, intersections)
236
+ t := checker.Checker_getApparentType(ctx.TypeChecker, t)
237
+
238
+ // Get awaited type (unwraps Promise)
239
+ t := checker.Checker_getAwaitedType(ctx.TypeChecker, t)
240
+
241
+ // Get type from a type annotation node
242
+ t := checker.Checker_getTypeFromTypeNode(ctx.TypeChecker, typeNode)
243
+ ```
244
+
245
+ ### Type Flag Checks
246
+
247
+ TypeFlags are bitmasks — check with `utils.IsTypeFlagSet`:
248
+
249
+ ```go
250
+ // Check specific flags
251
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsVoid) { return }
252
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsUndefined) { return }
253
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsNever) { return }
254
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsAny) { return }
255
+
256
+ // Combine flags with |
257
+ if utils.IsTypeFlagSet(t, checker.TypeFlagsVoid|checker.TypeFlagsUndefined|checker.TypeFlagsNever) {
258
+ return // skip void, undefined, and never
259
+ }
260
+
261
+ // Convenience helpers
262
+ utils.IsTypeAnyType(t)
263
+ utils.IsTypeUnknownType(t)
264
+ utils.IsObjectType(t)
265
+ utils.IsTypeParameter(t)
266
+ ```
267
+
268
+ ### Union and Intersection Types
269
+
270
+ **Decomposing unions is the most common pattern** — 58 uses across all rules:
271
+
272
+ ```go
273
+ // Iterate over union parts: `Error | string` → [Error, string]
274
+ for _, part := range utils.UnionTypeParts(t) {
275
+ if utils.IsErrorLike(ctx.Program, ctx.TypeChecker, part) {
276
+ hasError = true
277
+ break
278
+ }
279
+ }
280
+
281
+ // Check if it's a union type
282
+ if utils.IsUnionType(t) { ... }
283
+ if utils.IsIntersectionType(t) { ... }
284
+
285
+ // Iterate intersection parts
286
+ for _, part := range utils.IntersectionTypeParts(t) { ... }
287
+
288
+ // Recursive predicate check across union/intersection
289
+ result := utils.TypeRecurser(t, func(t *checker.Type) bool {
290
+ return utils.IsTypeAnyType(t)
291
+ })
292
+ ```
293
+
294
+ ### Built-in Type Checks
295
+
296
+ ```go
297
+ // Error types
298
+ utils.IsErrorLike(ctx.Program, ctx.TypeChecker, t)
299
+ utils.IsReadonlyErrorLike(ctx.Program, ctx.TypeChecker, t)
300
+
301
+ // Promise types
302
+ utils.IsPromiseLike(ctx.Program, ctx.TypeChecker, t)
303
+ utils.IsThenableType(ctx.TypeChecker, node, t)
304
+
305
+ // Array types
306
+ checker.Checker_isArrayType(ctx.TypeChecker, t)
307
+ checker.IsTupleType(t)
308
+ checker.Checker_isArrayOrTupleType(ctx.TypeChecker, t)
309
+
310
+ // Generic built-in matching
311
+ utils.IsBuiltinSymbolLike(ctx.Program, ctx.TypeChecker, t, "Function")
312
+ utils.IsBuiltinSymbolLike(ctx.Program, ctx.TypeChecker, t, "RegExp")
313
+ utils.IsBuiltinSymbolLike(ctx.Program, ctx.TypeChecker, t, "ReadonlyArray")
314
+ ```
315
+
316
+ ### Type Properties and Signatures
317
+
318
+ ```go
319
+ // Get a named property from a type
320
+ prop := checker.Checker_getPropertyOfType(ctx.TypeChecker, t, "then")
321
+ if prop != nil {
322
+ propType := ctx.TypeChecker.GetTypeOfSymbolAtLocation(prop, node)
323
+ }
324
+
325
+ // Get all properties
326
+ props := checker.Checker_getPropertiesOfType(ctx.TypeChecker, t)
327
+
328
+ // Get call signatures (for callable types)
329
+ sigs := utils.GetCallSignatures(ctx.TypeChecker, t)
330
+ // or
331
+ sigs := ctx.TypeChecker.GetCallSignatures(t)
332
+
333
+ // Get signature parameters
334
+ params := checker.Signature_parameters(sig)
335
+
336
+ // Get return type of a signature
337
+ returnType := checker.Checker_getReturnTypeOfSignature(ctx.TypeChecker, sig)
338
+
339
+ // Get type arguments (for generics, arrays, tuples)
340
+ typeArgs := checker.Checker_getTypeArguments(ctx.TypeChecker, t)
341
+
342
+ // Get resolved call signature at a call site
343
+ sig := checker.Checker_getResolvedSignature(ctx.TypeChecker, callNode)
344
+ ```
345
+
346
+ ### Type Assignability
347
+
348
+ ```go
349
+ // Check if source is assignable to target
350
+ if checker.Checker_isTypeAssignableTo(ctx.TypeChecker, sourceType, targetType) {
351
+ // source extends target
352
+ }
353
+
354
+ // Get base constraint of a type parameter
355
+ constraint := checker.Checker_getBaseConstraintOfType(ctx.TypeChecker, t)
356
+ ```
357
+
358
+ ### Symbols
359
+
360
+ ```go
361
+ // Get symbol at a location
362
+ symbol := ctx.TypeChecker.GetSymbolAtLocation(node)
363
+
364
+ // Get declaration for a symbol
365
+ decl := utils.GetDeclaration(ctx.TypeChecker, node)
366
+
367
+ // Get type from symbol
368
+ t := checker.Checker_getTypeOfSymbol(ctx.TypeChecker, symbol)
369
+ t := checker.Checker_getDeclaredTypeOfSymbol(ctx.TypeChecker, symbol)
370
+
371
+ // Check if symbol comes from default library
372
+ utils.IsSymbolFromDefaultLibrary(ctx.Program, symbol)
373
+
374
+ // Get the accessed property name (works with computed properties too)
375
+ name, ok := checker.Checker_getAccessedPropertyName(ctx.TypeChecker, node)
376
+ ```
377
+
378
+ ### Formatting Types for Error Messages
379
+
380
+ ```go
381
+ typeName := ctx.TypeChecker.TypeToString(t)
382
+ // → "string", "Error | User", "Promise<number>", etc.
383
+
384
+ // Shorter type name helper
385
+ name := utils.GetTypeName(ctx.TypeChecker, t)
386
+ ```
387
+
388
+ ## AST Navigation
389
+
390
+ ### Node Casting
391
+
392
+ Every AST node is `*ast.Node`. Use `.AsXxx()` to access specific fields:
393
+
394
+ ```go
395
+ call := node.AsCallExpression()
396
+ call.Expression // the callee
397
+ call.Arguments // argument list
398
+
399
+ binary := node.AsBinaryExpression()
400
+ binary.Left
401
+ binary.Right
402
+ binary.OperatorToken.Kind // ast.KindEqualsToken, ast.KindPlusToken, etc.
403
+
404
+ prop := node.AsPropertyAccessExpression()
405
+ prop.Expression // object
406
+ prop.Name() // property name node
407
+ ```
408
+
409
+ ### Type Predicates
410
+
411
+ ```go
412
+ ast.IsCallExpression(node)
413
+ ast.IsPropertyAccessExpression(node)
414
+ ast.IsIdentifier(node)
415
+ ast.IsAccessExpression(node) // property OR element access
416
+ ast.IsBinaryExpression(node)
417
+ ast.IsAssignmentExpression(node, includeCompound) // a = b, a += b
418
+ ast.IsVoidExpression(node)
419
+ ast.IsAwaitExpression(node)
420
+ ast.IsFunctionLike(node)
421
+ ast.IsArrowFunction(node)
422
+ ast.IsStringLiteral(node)
423
+ ```
424
+
425
+ ### Skipping Parentheses
426
+
427
+ Always skip parentheses when analyzing expression content:
428
+
429
+ ```go
430
+ expression := ast.SkipParentheses(node.AsExpressionStatement().Expression)
431
+ ```
432
+
433
+ ### Walking Parents
434
+
435
+ ```go
436
+ parent := node.Parent
437
+ for parent != nil {
438
+ if ast.IsCallExpression(parent) {
439
+ // node is inside a call expression
440
+ break
441
+ }
442
+ parent = parent.Parent
443
+ }
444
+ ```
445
+
446
+ ## Reporting Errors
447
+
448
+ ### Simple Error
449
+
450
+ ```go
451
+ ctx.ReportNode(node, rule.RuleMessage{
452
+ Id: "myErrorId", // unique ID for the error
453
+ Description: "Something is wrong.",
454
+ Help: "Optional longer explanation.", // shown as help text
455
+ })
456
+ ```
457
+
458
+ ### Error with Auto-Fix
459
+
460
+ Fixes are applied automatically by the linter:
461
+
462
+ ```go
463
+ ctx.ReportNodeWithFixes(node, msg, func() []rule.RuleFix {
464
+ return []rule.RuleFix{
465
+ rule.RuleFixInsertBefore(ctx.SourceFile, node, "await "),
466
+ }
467
+ })
468
+ ```
469
+
470
+ ### Error with Suggestions
471
+
472
+ Suggestions require user confirmation:
473
+
474
+ ```go
475
+ ctx.ReportNodeWithSuggestions(node, msg, func() []rule.RuleSuggestion {
476
+ return []rule.RuleSuggestion{{
477
+ Message: rule.RuleMessage{Id: "addAwait", Description: "Add await"},
478
+ FixesArr: []rule.RuleFix{
479
+ rule.RuleFixInsertBefore(ctx.SourceFile, node, "await "),
480
+ },
481
+ }}
482
+ })
483
+ ```
484
+
485
+ ### Error with Multiple Labeled Ranges
486
+
487
+ Highlight multiple code locations:
488
+
489
+ ```go
490
+ ctx.ReportDiagnostic(rule.RuleDiagnostic{
491
+ Range: exprRange,
492
+ Message: rule.RuleMessage{Id: "typeMismatch", Description: "Types are incompatible"},
493
+ LabeledRanges: []rule.RuleLabeledRange{
494
+ {Label: fmt.Sprintf("Type: %v", leftType), Range: leftRange},
495
+ {Label: fmt.Sprintf("Type: %v", rightType), Range: rightRange},
496
+ },
497
+ })
498
+ ```
499
+
500
+ ### Fix Helpers
501
+
502
+ ```go
503
+ // Insert text before a node
504
+ rule.RuleFixInsertBefore(ctx.SourceFile, node, "await ")
505
+
506
+ // Insert text after a node
507
+ rule.RuleFixInsertAfter(node, ")")
508
+
509
+ // Replace a node with text
510
+ rule.RuleFixReplace(ctx.SourceFile, node, "newCode")
511
+
512
+ // Remove a node
513
+ rule.RuleFixRemove(ctx.SourceFile, node)
514
+
515
+ // Replace a specific text range
516
+ rule.RuleFixReplaceRange(textRange, "replacement")
517
+
518
+ // Remove a specific text range
519
+ rule.RuleFixRemoveRange(textRange)
520
+ ```
521
+
522
+ ### Getting Token Ranges for Fixes
523
+
524
+ When you need the exact range of a keyword token (like `void`, `as`, `await`):
525
+
526
+ ```go
527
+ import "github.com/microsoft/typescript-go/shim/scanner"
528
+
529
+ // Get range of token at a position
530
+ voidTokenRange := scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, node.Pos())
531
+
532
+ // Get a scanner to scan forward
533
+ s := scanner.GetScannerForSourceFile(ctx.SourceFile, startPos)
534
+ tokenRange := s.TokenRange()
535
+ ```
536
+
537
+ ## Rule Options
538
+
539
+ Rules can accept configuration via JSON:
540
+
541
+ ```go
542
+ var MyRule = rule.Rule{
543
+ Name: "my-rule",
544
+ Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
545
+ opts := utils.UnmarshalOptions[MyRuleOptions](options, "my-rule")
546
+ // opts is now typed
547
+ },
548
+ }
549
+
550
+ type MyRuleOptions struct {
551
+ IgnoreVoid bool `json:"ignoreVoid"`
552
+ AllowedTypes []string `json:"allowedTypes"`
553
+ }
554
+ ```
555
+
556
+ For lintcn rules, define the options struct directly in your rule file or
557
+ in a separate `options.go` file in the same subfolder.
558
+
559
+ ## State Tracking (Scope Stacks)
560
+
561
+ When you need to track state across function boundaries (like "does this
562
+ function contain an await?"), use enter/exit listener pairs with a linked
563
+ list as a stack:
564
+
565
+ ```go
566
+ type scopeInfo struct {
567
+ hasAwait bool
568
+ upper *scopeInfo
569
+ }
570
+ var currentScope *scopeInfo
571
+
572
+ enterFunc := func(node *ast.Node) {
573
+ currentScope = &scopeInfo{upper: currentScope}
574
+ }
575
+
576
+ exitFunc := func(node *ast.Node) {
577
+ if !currentScope.hasAwait {
578
+ ctx.ReportNode(node, msg)
579
+ }
580
+ currentScope = currentScope.upper
581
+ }
582
+
583
+ return rule.RuleListeners{
584
+ ast.KindFunctionDeclaration: enterFunc,
585
+ rule.ListenerOnExit(ast.KindFunctionDeclaration): exitFunc,
586
+ ast.KindArrowFunction: enterFunc,
587
+ rule.ListenerOnExit(ast.KindArrowFunction): exitFunc,
588
+ ast.KindAwaitExpression: func(node *ast.Node) {
589
+ currentScope.hasAwait = true
590
+ },
591
+ }
592
+ ```
593
+
594
+ ## Testing
595
+
596
+ Tests use `rule_tester.RunRuleTester` which creates a TypeScript program from
597
+ inline code and runs the rule against it. The test file must use the same
598
+ package name as the rule:
599
+
600
+ ```go
601
+ // .lintcn/my_rule/my_rule_test.go
602
+ package my_rule
603
+
604
+ import (
605
+ "testing"
606
+ "github.com/typescript-eslint/tsgolint/internal/rule_tester"
607
+ "github.com/typescript-eslint/tsgolint/internal/rules/fixtures"
608
+ )
609
+
610
+ func TestMyRule(t *testing.T) {
611
+ t.Parallel()
612
+ rule_tester.RunRuleTester(
613
+ fixtures.GetRootDir(),
614
+ "tsconfig.minimal.json",
615
+ t,
616
+ &MyRule,
617
+ validCases,
618
+ invalidCases,
619
+ )
620
+ }
621
+ ```
622
+
623
+ ### Valid Test Cases (should NOT trigger)
624
+
625
+ ```go
626
+ var validCases = []rule_tester.ValidTestCase{
627
+ {Code: `const x = getUser("id");`},
628
+ {Code: `void dangerousCall();`},
629
+ // tsx support
630
+ {Code: `<div onClick={() => {}} />`, Tsx: true},
631
+ // custom filename
632
+ {Code: `import x from './foo'`, FileName: "index.ts"},
633
+ // with rule options
634
+ {Code: `getUser("id");`, Options: MyRuleOptions{IgnoreVoid: true}},
635
+ // with extra files for multi-file tests
636
+ {
637
+ Code: `import { x } from './helper';`,
638
+ Files: map[string]string{
639
+ "helper.ts": `export const x = 1;`,
640
+ },
641
+ },
642
+ }
643
+ ```
644
+
645
+ ### Invalid Test Cases (SHOULD trigger)
646
+
647
+ ```go
648
+ var invalidCases = []rule_tester.InvalidTestCase{
649
+ // Basic — just check the error fires
650
+ {
651
+ Code: `
652
+ declare function getUser(id: string): Error | { name: string };
653
+ getUser("id");
654
+ `,
655
+ Errors: []rule_tester.InvalidTestCaseError{
656
+ {MessageId: "noUnhandledError"},
657
+ },
658
+ },
659
+ // With exact position
660
+ {
661
+ Code: `getUser("id");`,
662
+ Errors: []rule_tester.InvalidTestCaseError{
663
+ {MessageId: "noUnhandledError", Line: 1, Column: 1, EndColumn: 15},
664
+ },
665
+ },
666
+ // With suggestions
667
+ {
668
+ Code: `
669
+ declare const arr: number[];
670
+ delete arr[0];
671
+ `,
672
+ Errors: []rule_tester.InvalidTestCaseError{
673
+ {
674
+ MessageId: "noArrayDelete",
675
+ Suggestions: []rule_tester.InvalidTestCaseSuggestion{
676
+ {
677
+ MessageId: "useSplice",
678
+ Output: `
679
+ declare const arr: number[];
680
+ arr.splice(0, 1);
681
+ `,
682
+ },
683
+ },
684
+ },
685
+ },
686
+ },
687
+ // With auto-fix output (code after fix applied)
688
+ {
689
+ Code: `const x = foo as any;`,
690
+ Output: []string{`const x = foo;`},
691
+ Errors: []rule_tester.InvalidTestCaseError{
692
+ {MessageId: "unsafeAssertion"},
693
+ },
694
+ },
695
+ }
696
+ ```
697
+
698
+ ### Important Test Details
699
+
700
+ - **MessageId** must match the `Id` field in your `rule.RuleMessage`
701
+ - **Line/Column** are 1-indexed, optional (omit for flexibility)
702
+ - **Output** is the code after ALL auto-fixes are applied (iterates up to 10 times)
703
+ - **Suggestions** check the output of each individual suggestion fix
704
+ - Tests run in parallel by default (`t.Parallel()`)
705
+ - Use `Only: true` on a test case to run only that test (like `.only` in vitest)
706
+ - Use `Skip: true` to skip a test case
707
+
708
+ ### Running Tests
709
+
710
+ ```bash
711
+ cd .lintcn
712
+ go test -v ./... # all tests
713
+ go test -v -run TestMyRule # specific test
714
+ go test -count=1 ./... # bypass test cache
715
+ ```
716
+
717
+ ### Snapshots
718
+
719
+ Tests generate snapshot files with the full diagnostic output — message text,
720
+ annotated source code, and underlined ranges. Run with `UPDATE_SNAPS=true` to
721
+ create or update them:
722
+
723
+ ```bash
724
+ # From the build workspace (found via `lintcn build` output path)
725
+ UPDATE_SNAPS=true go test -run TestMyRule -count=1 ./rules/my_rule/
726
+ ```
727
+
728
+ Snapshots are written to `internal/rule_tester/__snapshots__/{rule-name}.snap`
729
+ inside the cached tsgolint source. Copy them into your rule folder for reference:
730
+
731
+ ```
732
+ .lintcn/my_rule/__snapshots__/my-rule.snap
733
+ ```
734
+
735
+ **Always read the snapshot after writing tests** — it shows the exact messages
736
+ your rule produces, which is how you verify the output makes sense. Example
737
+ snapshot from `no-type-assertion`:
738
+
739
+ ```
740
+ [TestNoTypeAssertion/invalid-7 - 1]
741
+ Diagnostic 1: typeAssertion (4:14 - 4:22)
742
+ Message: Type assertion `as User ({ name: string; age: number })`.
743
+ The expression type is `Error | User`. Try removing the assertion
744
+ or narrowing the type instead.
745
+ 3 | declare const x: User | Error;
746
+ 4 | const y = x as User;
747
+ | ~~~~~~~~~
748
+ 5 |
749
+ ---
750
+
751
+ [TestNoTypeAssertion/invalid-8 - 1]
752
+ Diagnostic 1: typeAssertion (4:14 - 4:24)
753
+ Message: Type assertion `as Config ({ host: string; port: number })`.
754
+ The expression type is `Config | null`. Try removing the assertion
755
+ or narrowing the type instead.
756
+ 3 | declare const x: Config | null;
757
+ 4 | const y = x as Config;
758
+ | ~~~~~~~~~~~
759
+ 5 |
760
+ ---
761
+ ```
762
+
763
+ This shows: the message ID, position, full description text, and the source
764
+ code with the flagged range underlined. Use this to verify your error messages
765
+ are helpful and include enough type information for agents to act on.
766
+
767
+ ## Complete Rule Example: no-unhandled-error
768
+
769
+ A real rule that enforces the errore pattern — errors when a call expression
770
+ returns a type containing `Error` and the result is discarded:
771
+
772
+ ```go
773
+ // .lintcn/no_unhandled_error/no_unhandled_error.go
774
+
775
+ // lintcn:name no-unhandled-error
776
+ // lintcn:description Disallow discarding expressions that are subtypes of Error
777
+
778
+ package no_unhandled_error
779
+
780
+ import (
781
+ "github.com/microsoft/typescript-go/shim/ast"
782
+ "github.com/microsoft/typescript-go/shim/checker"
783
+ "github.com/typescript-eslint/tsgolint/internal/rule"
784
+ "github.com/typescript-eslint/tsgolint/internal/utils"
785
+ )
786
+
787
+ var NoUnhandledErrorRule = rule.Rule{
788
+ Name: "no-unhandled-error",
789
+ Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
790
+ return rule.RuleListeners{
791
+ ast.KindExpressionStatement: func(node *ast.Node) {
792
+ exprStatement := node.AsExpressionStatement()
793
+ expression := ast.SkipParentheses(exprStatement.Expression)
794
+
795
+ // void expressions are intentional discards
796
+ if ast.IsVoidExpression(expression) {
797
+ return
798
+ }
799
+
800
+ // only check call expressions and await expressions wrapping calls
801
+ innerExpr := expression
802
+ if ast.IsAwaitExpression(innerExpr) {
803
+ innerExpr = ast.SkipParentheses(innerExpr.Expression())
804
+ }
805
+ if !ast.IsCallExpression(innerExpr) {
806
+ return
807
+ }
808
+
809
+ t := ctx.TypeChecker.GetTypeAtLocation(expression)
810
+
811
+ // skip void, undefined, never
812
+ if utils.IsTypeFlagSet(t,
813
+ checker.TypeFlagsVoid|checker.TypeFlagsVoidLike|
814
+ checker.TypeFlagsUndefined|checker.TypeFlagsNever) {
815
+ return
816
+ }
817
+
818
+ // check if any union part is Error-like
819
+ for _, part := range utils.UnionTypeParts(t) {
820
+ if utils.IsErrorLike(ctx.Program, ctx.TypeChecker, part) {
821
+ ctx.ReportNode(node, rule.RuleMessage{
822
+ Id: "noUnhandledError",
823
+ Description: "Error-typed return value is not handled.",
824
+ })
825
+ return
826
+ }
827
+ }
828
+ },
829
+ }
830
+ },
831
+ }
832
+ ```
833
+
834
+ ## Go Workspace Setup
835
+
836
+ `.lintcn/` needs these generated files (created by `lintcn add` automatically):
837
+
838
+ **go.mod** — module name MUST be a child path of tsgolint for `internal/`
839
+ package access:
840
+
841
+ ```
842
+ module github.com/typescript-eslint/tsgolint/lintcn-rules
843
+
844
+ go 1.26
845
+ ```
846
+
847
+ **go.work** — workspace linking to cached tsgolint source:
848
+
849
+ ```
850
+ go 1.26
851
+
852
+ use (
853
+ .
854
+ ./.tsgolint
855
+ ./.tsgolint/typescript-go
856
+ )
857
+
858
+ replace (
859
+ github.com/microsoft/typescript-go/shim/ast => ./.tsgolint/shim/ast
860
+ github.com/microsoft/typescript-go/shim/checker => ./.tsgolint/shim/checker
861
+ // ... all 14 shim modules
862
+ )
863
+ ```
864
+
865
+ **.tsgolint/** — symlink to cached tsgolint clone (gitignored).
866
+
867
+ With this setup, gopls provides full autocomplete and go-to-definition on all
868
+ tsgolint and typescript-go APIs.