pi-lens 2.2.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/CHANGELOG.md +198 -0
  2. package/README.md +709 -519
  3. package/clients/__tests__/file-time.test.js +216 -0
  4. package/clients/__tests__/file-time.test.ts +276 -0
  5. package/clients/__tests__/format-service.test.js +245 -0
  6. package/clients/__tests__/format-service.test.ts +339 -0
  7. package/clients/__tests__/formatters.test.js +271 -0
  8. package/clients/__tests__/formatters.test.ts +401 -0
  9. package/clients/amain-types.js +164 -0
  10. package/clients/amain-types.ts +165 -0
  11. package/clients/architect-client.js +56 -12
  12. package/clients/architect-client.ts +81 -16
  13. package/clients/ast-grep-client.js +2 -2
  14. package/clients/ast-grep-client.ts +14 -39
  15. package/clients/ast-grep-parser.ts +1 -1
  16. package/clients/ast-grep-rule-manager.js +8 -0
  17. package/clients/ast-grep-rule-manager.ts +10 -1
  18. package/clients/ast-grep-types.js +9 -0
  19. package/clients/ast-grep-types.ts +106 -0
  20. package/clients/auto-loop.js +10 -0
  21. package/clients/auto-loop.ts +14 -1
  22. package/clients/biome-client.js +81 -19
  23. package/clients/biome-client.ts +103 -22
  24. package/clients/bus/bus.js +191 -0
  25. package/clients/bus/bus.ts +251 -0
  26. package/clients/bus/events.js +214 -0
  27. package/clients/bus/events.ts +279 -0
  28. package/clients/bus/index.js +8 -0
  29. package/clients/bus/index.ts +9 -0
  30. package/clients/bus/integration.js +158 -0
  31. package/clients/bus/integration.ts +214 -0
  32. package/clients/complexity-client.js +13 -7
  33. package/clients/complexity-client.ts +13 -7
  34. package/clients/config-validator.js +465 -0
  35. package/clients/config-validator.ts +558 -0
  36. package/clients/dependency-checker.js +4 -10
  37. package/clients/dependency-checker.ts +4 -10
  38. package/clients/dispatch/__tests__/autofix-integration.test.js +245 -0
  39. package/clients/dispatch/__tests__/autofix-integration.test.ts +300 -0
  40. package/clients/dispatch/__tests__/runner-registration.test.js +236 -0
  41. package/clients/dispatch/__tests__/runner-registration.test.ts +282 -0
  42. package/clients/dispatch/bus-dispatcher.js +177 -0
  43. package/clients/dispatch/bus-dispatcher.ts +251 -0
  44. package/clients/dispatch/dispatcher.edge.test.js +82 -0
  45. package/clients/dispatch/dispatcher.edge.test.ts +100 -0
  46. package/clients/dispatch/dispatcher.format.test.js +46 -0
  47. package/clients/dispatch/dispatcher.format.test.ts +58 -0
  48. package/clients/dispatch/dispatcher.inline.test.js +74 -0
  49. package/clients/dispatch/dispatcher.inline.test.ts +93 -0
  50. package/clients/dispatch/dispatcher.js +19 -53
  51. package/clients/dispatch/dispatcher.ts +20 -67
  52. package/clients/dispatch/plan.js +9 -4
  53. package/clients/dispatch/plan.ts +9 -4
  54. package/clients/dispatch/runners/architect.js +21 -7
  55. package/clients/dispatch/runners/architect.test.js +138 -0
  56. package/clients/dispatch/runners/architect.test.ts +162 -0
  57. package/clients/dispatch/runners/architect.ts +22 -7
  58. package/clients/dispatch/runners/ast-grep-napi.js +462 -0
  59. package/clients/dispatch/runners/ast-grep-napi.test.js +111 -0
  60. package/clients/dispatch/runners/ast-grep-napi.test.ts +133 -0
  61. package/clients/dispatch/runners/ast-grep-napi.ts +506 -0
  62. package/clients/dispatch/runners/ast-grep.js +62 -19
  63. package/clients/dispatch/runners/ast-grep.ts +70 -18
  64. package/clients/dispatch/runners/biome.js +29 -53
  65. package/clients/dispatch/runners/biome.ts +29 -63
  66. package/clients/dispatch/runners/config-validation.js +67 -0
  67. package/clients/dispatch/runners/config-validation.ts +82 -0
  68. package/clients/dispatch/runners/go-vet.js +4 -28
  69. package/clients/dispatch/runners/go-vet.ts +4 -32
  70. package/clients/dispatch/runners/index.js +30 -10
  71. package/clients/dispatch/runners/index.ts +30 -10
  72. package/clients/dispatch/runners/oxlint.js +141 -0
  73. package/clients/dispatch/runners/oxlint.test.js +230 -0
  74. package/clients/dispatch/runners/oxlint.test.ts +303 -0
  75. package/clients/dispatch/runners/oxlint.ts +175 -0
  76. package/clients/dispatch/runners/pyright.js +40 -70
  77. package/clients/dispatch/runners/pyright.test.js +16 -2
  78. package/clients/dispatch/runners/pyright.test.ts +14 -2
  79. package/clients/dispatch/runners/pyright.ts +48 -91
  80. package/clients/dispatch/runners/python-slop.js +97 -0
  81. package/clients/dispatch/runners/python-slop.test.js +203 -0
  82. package/clients/dispatch/runners/python-slop.test.ts +298 -0
  83. package/clients/dispatch/runners/python-slop.ts +124 -0
  84. package/clients/dispatch/runners/ruff.js +18 -71
  85. package/clients/dispatch/runners/ruff.ts +19 -79
  86. package/clients/dispatch/runners/rust-clippy.js +28 -32
  87. package/clients/dispatch/runners/rust-clippy.ts +29 -31
  88. package/clients/dispatch/runners/scan_codebase.test.js +89 -0
  89. package/clients/dispatch/runners/scan_codebase.test.ts +105 -0
  90. package/clients/dispatch/runners/shellcheck.js +147 -0
  91. package/clients/dispatch/runners/shellcheck.test.js +98 -0
  92. package/clients/dispatch/runners/shellcheck.test.ts +129 -0
  93. package/clients/dispatch/runners/shellcheck.ts +188 -0
  94. package/clients/dispatch/runners/similarity.js +230 -0
  95. package/clients/dispatch/runners/similarity.ts +339 -0
  96. package/clients/dispatch/runners/spellcheck.js +106 -0
  97. package/clients/dispatch/runners/spellcheck.test.js +158 -0
  98. package/clients/dispatch/runners/spellcheck.test.ts +214 -0
  99. package/clients/dispatch/runners/spellcheck.ts +136 -0
  100. package/clients/dispatch/runners/tree-sitter.js +107 -0
  101. package/clients/dispatch/runners/tree-sitter.ts +135 -0
  102. package/clients/dispatch/runners/ts-lsp.js +104 -33
  103. package/clients/dispatch/runners/ts-lsp.ts +120 -38
  104. package/clients/dispatch/runners/ts-slop.js +113 -0
  105. package/clients/dispatch/runners/ts-slop.test.js +180 -0
  106. package/clients/dispatch/runners/ts-slop.test.ts +230 -0
  107. package/clients/dispatch/runners/ts-slop.ts +142 -0
  108. package/clients/dispatch/runners/utils/diagnostic-parsers.js +134 -0
  109. package/clients/dispatch/runners/utils/diagnostic-parsers.ts +186 -0
  110. package/clients/dispatch/runners/utils/runner-helpers.js +115 -0
  111. package/clients/dispatch/runners/utils/runner-helpers.ts +167 -0
  112. package/clients/dispatch/runners/utils.js +2 -4
  113. package/clients/dispatch/runners/utils.ts +2 -4
  114. package/clients/dispatch/types.ts +1 -1
  115. package/clients/dispatch/utils/format-utils.js +49 -0
  116. package/clients/dispatch/utils/format-utils.ts +60 -0
  117. package/clients/dogfood.test.js +201 -0
  118. package/clients/dogfood.test.ts +269 -0
  119. package/clients/file-time.js +152 -0
  120. package/clients/file-time.ts +208 -0
  121. package/clients/file-utils.js +40 -0
  122. package/clients/file-utils.ts +44 -0
  123. package/clients/fix-scanners.js +10 -20
  124. package/clients/fix-scanners.ts +10 -22
  125. package/clients/format-service.js +172 -0
  126. package/clients/format-service.ts +254 -0
  127. package/clients/formatters.js +435 -0
  128. package/clients/formatters.ts +508 -0
  129. package/clients/go-client.js +5 -14
  130. package/clients/go-client.ts +5 -13
  131. package/clients/installer/index.js +356 -0
  132. package/clients/installer/index.ts +426 -0
  133. package/clients/jscpd-client.js +11 -9
  134. package/clients/jscpd-client.ts +12 -8
  135. package/clients/knip-client.js +3 -7
  136. package/clients/knip-client.ts +3 -6
  137. package/clients/lsp/__tests__/client.test.js +325 -0
  138. package/clients/lsp/__tests__/client.test.ts +434 -0
  139. package/clients/lsp/__tests__/config.test.js +166 -0
  140. package/clients/lsp/__tests__/config.test.ts +209 -0
  141. package/clients/lsp/__tests__/error-recovery.test.js +213 -0
  142. package/clients/lsp/__tests__/error-recovery.test.ts +279 -0
  143. package/clients/lsp/__tests__/integration.test.js +127 -0
  144. package/clients/lsp/__tests__/integration.test.ts +160 -0
  145. package/clients/lsp/__tests__/launch.test.js +260 -0
  146. package/clients/lsp/__tests__/launch.test.ts +329 -0
  147. package/clients/lsp/__tests__/server.test.js +259 -0
  148. package/clients/lsp/__tests__/server.test.ts +332 -0
  149. package/clients/lsp/__tests__/service.test.js +417 -0
  150. package/clients/lsp/__tests__/service.test.ts +499 -0
  151. package/clients/lsp/client.js +235 -0
  152. package/clients/lsp/client.ts +328 -0
  153. package/clients/lsp/config.js +115 -0
  154. package/clients/lsp/config.ts +149 -0
  155. package/clients/lsp/index.js +222 -0
  156. package/clients/lsp/index.ts +280 -0
  157. package/clients/lsp/installer/index.js +391 -0
  158. package/clients/lsp/interactive-install.js +210 -0
  159. package/clients/lsp/interactive-install.ts +251 -0
  160. package/clients/lsp/language.js +170 -0
  161. package/clients/lsp/language.ts +216 -0
  162. package/clients/lsp/launch.js +174 -0
  163. package/clients/lsp/launch.ts +240 -0
  164. package/clients/lsp/lsp/launch.js +116 -0
  165. package/clients/lsp/lsp/server.js +532 -0
  166. package/clients/lsp/lsp-index.js +10 -0
  167. package/clients/lsp/lsp-index.ts +11 -0
  168. package/clients/lsp/path-utils.js +48 -0
  169. package/clients/lsp/path-utils.ts +52 -0
  170. package/clients/lsp/server.js +615 -0
  171. package/clients/lsp/server.ts +800 -0
  172. package/clients/lsp/test-py-spawn/requirements.txt +1 -0
  173. package/clients/lsp/test-py-spawn/test.py +3 -0
  174. package/clients/lsp/test-py-svc/requirements.txt +1 -0
  175. package/clients/lsp/test-py-svc/test.py +3 -0
  176. package/clients/lsp/test-python-project/requirements.txt +1 -0
  177. package/clients/lsp/test-python-project/test.py +5 -0
  178. package/clients/metrics-history.js +2 -2
  179. package/clients/metrics-history.ts +2 -2
  180. package/clients/production-readiness.js +522 -0
  181. package/clients/production-readiness.ts +556 -0
  182. package/clients/project-index.js +255 -0
  183. package/clients/project-index.ts +383 -0
  184. package/clients/project-metadata.js +531 -0
  185. package/clients/project-metadata.ts +624 -0
  186. package/clients/ruff-client.js +56 -16
  187. package/clients/ruff-client.ts +72 -15
  188. package/clients/runner-tracker.js +152 -0
  189. package/clients/runner-tracker.ts +213 -0
  190. package/clients/rust-client.js +4 -11
  191. package/clients/rust-client.ts +5 -11
  192. package/clients/safe-spawn.js +96 -0
  193. package/clients/safe-spawn.ts +128 -0
  194. package/clients/scan-architectural-debt.js +3 -6
  195. package/clients/scan-architectural-debt.ts +3 -6
  196. package/clients/scan-utils.js +5 -20
  197. package/clients/scan-utils.ts +5 -29
  198. package/clients/secrets-scanner.js +3 -17
  199. package/clients/secrets-scanner.ts +4 -20
  200. package/clients/services/__tests__/effect-integration.test.js +86 -0
  201. package/clients/services/__tests__/effect-integration.test.ts +111 -0
  202. package/clients/services/effect-integration.js +194 -0
  203. package/clients/services/effect-integration.ts +268 -0
  204. package/clients/services/index.js +7 -0
  205. package/clients/services/index.ts +8 -0
  206. package/clients/services/runner-service.js +105 -0
  207. package/clients/services/runner-service.ts +179 -0
  208. package/clients/sg-runner.js +87 -13
  209. package/clients/sg-runner.ts +97 -13
  210. package/clients/state-matrix.js +160 -0
  211. package/clients/state-matrix.ts +202 -0
  212. package/clients/subprocess-client.js +10 -9
  213. package/clients/subprocess-client.ts +10 -8
  214. package/clients/test-runner-client.js +3 -7
  215. package/clients/test-runner-client.ts +3 -6
  216. package/clients/tool-availability.js +4 -10
  217. package/clients/tool-availability.ts +4 -9
  218. package/clients/tree-sitter-client.js +564 -0
  219. package/clients/tree-sitter-client.ts +797 -0
  220. package/clients/tree-sitter-query-loader.js +355 -0
  221. package/clients/tree-sitter-query-loader.ts +425 -0
  222. package/clients/type-coverage-client.js +3 -7
  223. package/clients/type-coverage-client.ts +3 -6
  224. package/clients/typescript-client.codefix.test.js +157 -0
  225. package/clients/typescript-client.codefix.test.ts +186 -0
  226. package/clients/typescript-client.js +43 -0
  227. package/clients/typescript-client.ts +98 -0
  228. package/commands/booboo.js +799 -219
  229. package/commands/booboo.ts +1004 -225
  230. package/commands/clients/ast-grep-client.js +250 -0
  231. package/commands/clients/ast-grep-parser.js +86 -0
  232. package/commands/clients/ast-grep-rule-manager.js +91 -0
  233. package/commands/clients/ast-grep-types.js +9 -0
  234. package/commands/clients/biome-client.js +380 -0
  235. package/commands/clients/complexity-client.js +667 -0
  236. package/commands/clients/file-kinds.js +177 -0
  237. package/commands/clients/file-utils.js +40 -0
  238. package/commands/clients/jscpd-client.js +169 -0
  239. package/commands/clients/knip-client.js +211 -0
  240. package/commands/clients/ruff-client.js +297 -0
  241. package/commands/clients/safe-spawn.js +88 -0
  242. package/commands/clients/scan-utils.js +83 -0
  243. package/commands/clients/sg-runner.js +190 -0
  244. package/commands/clients/types.js +11 -0
  245. package/commands/clients/typescript-client.js +505 -0
  246. package/commands/fix-from-booboo.js +398 -0
  247. package/commands/fix-from-booboo.ts +485 -0
  248. package/commands/fix-simplified.js +618 -0
  249. package/commands/fix-simplified.ts +768 -0
  250. package/commands/rate.js +10 -14
  251. package/commands/rate.ts +9 -16
  252. package/default-architect.yaml +59 -15
  253. package/index.ts +342 -429
  254. package/package.json +16 -3
  255. package/rules/ast-grep-rules/rules/empty-catch.yml +38 -13
  256. package/rules/ast-grep-rules/rules/no-array-constructor.yml +1 -0
  257. package/rules/ast-grep-rules/rules/no-debugger.yml +2 -0
  258. package/rules/python-slop-rules/.sgconfig.yml +4 -0
  259. package/rules/python-slop-rules/rules/slop-rules.yml +647 -0
  260. package/rules/tree-sitter-queries/python/bare-except.yml +54 -0
  261. package/rules/tree-sitter-queries/python/eval-exec.yml +50 -0
  262. package/rules/tree-sitter-queries/python/is-vs-equals.yml +60 -0
  263. package/rules/tree-sitter-queries/python/mutable-default-arg.yml +57 -0
  264. package/rules/tree-sitter-queries/python/unreachable-except.yml +60 -0
  265. package/rules/tree-sitter-queries/python/wildcard-import.yml +46 -0
  266. package/rules/tree-sitter-queries/tsx/dangerously-set-inner-html.yml +63 -0
  267. package/rules/tree-sitter-queries/typescript/await-in-loop.yml +56 -0
  268. package/rules/tree-sitter-queries/typescript/console-statement.yml +47 -0
  269. package/rules/tree-sitter-queries/typescript/debugger.yml +47 -0
  270. package/rules/tree-sitter-queries/typescript/deep-nesting.yml +117 -0
  271. package/rules/tree-sitter-queries/typescript/deep-promise-chain.yml +73 -0
  272. package/rules/tree-sitter-queries/typescript/empty-catch.yml +64 -0
  273. package/rules/tree-sitter-queries/typescript/eval.yml +48 -0
  274. package/rules/tree-sitter-queries/typescript/hardcoded-secrets.yml +78 -0
  275. package/rules/tree-sitter-queries/typescript/long-parameter-list.yml +62 -0
  276. package/rules/tree-sitter-queries/typescript/mixed-async-styles.yml +49 -0
  277. package/rules/tree-sitter-queries/typescript/nested-ternary.yml +45 -0
  278. package/rules/ts-slop-rules/.sgconfig.yml +4 -0
  279. package/rules/ts-slop-rules/rules/in-correct-optional-input-type.yml +10 -0
  280. package/rules/ts-slop-rules/rules/jwt-no-verify.yml +13 -0
  281. package/rules/ts-slop-rules/rules/no-architecture-violation.yml +10 -0
  282. package/rules/ts-slop-rules/rules/no-case-declarations.yml +10 -0
  283. package/rules/ts-slop-rules/rules/no-dangerously-set-inner-html.yml +10 -0
  284. package/rules/ts-slop-rules/rules/no-debugger.yml +10 -0
  285. package/rules/ts-slop-rules/rules/no-dupe-args.yml +10 -0
  286. package/rules/ts-slop-rules/rules/no-dupe-class-members.yml +10 -0
  287. package/rules/ts-slop-rules/rules/no-dupe-keys.yml +10 -0
  288. package/rules/ts-slop-rules/rules/no-eval.yml +13 -0
  289. package/rules/ts-slop-rules/rules/no-hardcoded-secrets.yml +12 -0
  290. package/rules/ts-slop-rules/rules/no-implied-eval.yml +12 -0
  291. package/rules/ts-slop-rules/rules/no-inner-html.yml +13 -0
  292. package/rules/ts-slop-rules/rules/no-javascript-url.yml +10 -0
  293. package/rules/ts-slop-rules/rules/no-mutable-default.yml +10 -0
  294. package/rules/ts-slop-rules/rules/no-nested-links.yml +12 -0
  295. package/rules/ts-slop-rules/rules/no-new-symbol.yml +10 -0
  296. package/rules/ts-slop-rules/rules/no-new-wrappers.yml +13 -0
  297. package/rules/ts-slop-rules/rules/no-open-redirect.yml +16 -0
  298. package/rules/ts-slop-rules/rules/slop-rules.yml +455 -0
  299. package/rules/ts-slop-rules/rules/weak-rsa-key.yml +12 -0
  300. package/skills/ast-grep/SKILL.md +182 -0
  301. package/clients/dispatch/runners/secrets.js +0 -109
  302. package/commands/fix.js +0 -244
  303. package/commands/fix.ts +0 -373
  304. package/rules/ast-grep-rules/rules/no-lonely-if.yml +0 -13
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Formatter Detection Tests
3
+ *
4
+ * Tests smart detection of formatters based on:
5
+ * - Config files (biome.json, .prettierrc, etc.)
6
+ * - Dependencies (package.json, requirements.txt)
7
+ * - Binary availability
8
+ */
9
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ import { biomeFormatter, prettierFormatter, ruffFormatter, blackFormatter, gofmtFormatter, rustfmtFormatter, getFormattersForFile, clearFormatterCache, formatFile, } from "../formatters.js";
13
+ import { fileURLToPath } from "url";
14
+ import { dirname } from "path";
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const TEST_DIR = path.join(__dirname, "..", "..", "test-formatters");
18
+ describe("Formatter Detection", () => {
19
+ beforeEach(() => {
20
+ clearFormatterCache();
21
+ if (fs.existsSync(TEST_DIR)) {
22
+ fs.rmSync(TEST_DIR, { recursive: true });
23
+ }
24
+ fs.mkdirSync(TEST_DIR, { recursive: true });
25
+ });
26
+ afterEach(() => {
27
+ clearFormatterCache();
28
+ if (fs.existsSync(TEST_DIR)) {
29
+ fs.rmSync(TEST_DIR, { recursive: true });
30
+ }
31
+ });
32
+ describe("biomeFormatter.detect()", () => {
33
+ it("should detect biome.json config file", async () => {
34
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), '{"formatter": {}}');
35
+ const detected = await biomeFormatter.detect(TEST_DIR);
36
+ expect(detected).toBe(true);
37
+ });
38
+ it("should detect biome.jsonc config file", async () => {
39
+ fs.writeFileSync(path.join(TEST_DIR, "biome.jsonc"), "{}");
40
+ const detected = await biomeFormatter.detect(TEST_DIR);
41
+ expect(detected).toBe(true);
42
+ });
43
+ it("should detect @biomejs/biome in devDependencies", async () => {
44
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { "@biomejs/biome": "^1.0.0" } }));
45
+ const detected = await biomeFormatter.detect(TEST_DIR);
46
+ expect(detected).toBe(true);
47
+ });
48
+ it("should return false when no biome config", async () => {
49
+ const detected = await biomeFormatter.detect(TEST_DIR);
50
+ expect(detected).toBe(false);
51
+ });
52
+ it("should find biome.json in parent directory", async () => {
53
+ const subDir = path.join(TEST_DIR, "src", "components");
54
+ fs.mkdirSync(subDir, { recursive: true });
55
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
56
+ const detected = await biomeFormatter.detect(subDir);
57
+ expect(detected).toBe(true);
58
+ });
59
+ });
60
+ describe("prettierFormatter.detect()", () => {
61
+ it("should detect .prettierrc config file", async () => {
62
+ fs.writeFileSync(path.join(TEST_DIR, ".prettierrc"), "{}");
63
+ const detected = await prettierFormatter.detect(TEST_DIR);
64
+ expect(detected).toBe(true);
65
+ });
66
+ it("should detect prettier in devDependencies", async () => {
67
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
68
+ const detected = await prettierFormatter.detect(TEST_DIR);
69
+ expect(detected).toBe(true);
70
+ });
71
+ it("should detect prettier field in package.json", async () => {
72
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ prettier: { semi: false } }));
73
+ const detected = await prettierFormatter.detect(TEST_DIR);
74
+ expect(detected).toBe(true);
75
+ });
76
+ it("should return false when no prettier config", async () => {
77
+ const detected = await prettierFormatter.detect(TEST_DIR);
78
+ expect(detected).toBe(false);
79
+ });
80
+ });
81
+ describe("ruffFormatter.detect()", () => {
82
+ it("should detect [tool.ruff] in pyproject.toml", async () => {
83
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.ruff]\nline-length = 100");
84
+ const detected = await ruffFormatter.detect(TEST_DIR);
85
+ expect(detected).toBe(true);
86
+ });
87
+ it("should detect ruff.toml config file", async () => {
88
+ fs.writeFileSync(path.join(TEST_DIR, "ruff.toml"), "line-length = 100");
89
+ const detected = await ruffFormatter.detect(TEST_DIR);
90
+ expect(detected).toBe(true);
91
+ });
92
+ it("should detect .ruff.toml config file", async () => {
93
+ fs.writeFileSync(path.join(TEST_DIR, ".ruff.toml"), "line-length = 100");
94
+ const detected = await ruffFormatter.detect(TEST_DIR);
95
+ expect(detected).toBe(true);
96
+ });
97
+ it("should detect ruff in requirements.txt", async () => {
98
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff==0.1.0\n");
99
+ const detected = await ruffFormatter.detect(TEST_DIR);
100
+ expect(detected).toBe(true);
101
+ });
102
+ it("should detect ruff even if [tool.black] exists (no preference logic)", async () => {
103
+ // Create pyproject.toml with black config
104
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
105
+ // Also write requirements with ruff
106
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff\n");
107
+ // The current implementation doesn't have preference logic
108
+ // Both black and ruff would be detected if their configs exist
109
+ // This is intentional - users can disable one if needed
110
+ const detected = await blackFormatter.detect(TEST_DIR);
111
+ expect(detected).toBe(true);
112
+ });
113
+ });
114
+ describe("blackFormatter.detect()", () => {
115
+ it("should detect [tool.black] in pyproject.toml", async () => {
116
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
117
+ const detected = await blackFormatter.detect(TEST_DIR);
118
+ expect(detected).toBe(true);
119
+ });
120
+ it("should detect black in requirements.txt", async () => {
121
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "black==23.0.0\n");
122
+ const detected = await blackFormatter.detect(TEST_DIR);
123
+ expect(detected).toBe(true);
124
+ });
125
+ it("should return false when no black config", async () => {
126
+ const detected = await blackFormatter.detect(TEST_DIR);
127
+ expect(detected).toBe(false);
128
+ });
129
+ });
130
+ describe("gofmtFormatter.detect()", () => {
131
+ it("should detect gofmt binary availability", async () => {
132
+ // This test depends on whether gofmt is installed
133
+ // We can't reliably test this in CI, but we can verify the logic
134
+ const detected = await gofmtFormatter.detect(TEST_DIR);
135
+ // Should return boolean based on binary availability
136
+ expect(typeof detected).toBe("boolean");
137
+ });
138
+ });
139
+ describe("rustfmtFormatter.detect()", () => {
140
+ it("should detect rustfmt binary availability", async () => {
141
+ const detected = await rustfmtFormatter.detect(TEST_DIR);
142
+ expect(typeof detected).toBe("boolean");
143
+ });
144
+ });
145
+ describe("getFormattersForFile()", () => {
146
+ it("should return formatters for TypeScript file with biome config", async () => {
147
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
148
+ const tsFile = path.join(TEST_DIR, "test.ts");
149
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
150
+ expect(formatters.map(f => f.name)).toContain("biome");
151
+ });
152
+ it("should return formatters for TypeScript file with prettier", async () => {
153
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
154
+ const tsFile = path.join(TEST_DIR, "test.ts");
155
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
156
+ expect(formatters.map(f => f.name)).toContain("prettier");
157
+ });
158
+ it("should return multiple formatters for TypeScript file", async () => {
159
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
160
+ fs.writeFileSync(path.join(TEST_DIR, "package.json"), JSON.stringify({ devDependencies: { prettier: "^3.0.0" } }));
161
+ const tsFile = path.join(TEST_DIR, "test.ts");
162
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
163
+ // Both biome and prettier should be returned
164
+ expect(formatters.length).toBeGreaterThanOrEqual(1);
165
+ const names = formatters.map(f => f.name);
166
+ expect(names).toContain("biome");
167
+ });
168
+ it("should return ruff for Python file with pyproject.toml", async () => {
169
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.ruff]\nline-length = 100");
170
+ const pyFile = path.join(TEST_DIR, "test.py");
171
+ const formatters = await getFormattersForFile(pyFile, TEST_DIR);
172
+ expect(formatters.map(f => f.name)).toContain("ruff");
173
+ });
174
+ it("should return black for Python file with black config", async () => {
175
+ fs.writeFileSync(path.join(TEST_DIR, "pyproject.toml"), "[tool.black]\nline-length = 100");
176
+ const pyFile = path.join(TEST_DIR, "test.py");
177
+ const formatters = await getFormattersForFile(pyFile, TEST_DIR);
178
+ // Should prefer black over ruff
179
+ expect(formatters.map(f => f.name)).toContain("black");
180
+ });
181
+ it("should return empty array for unsupported extensions", async () => {
182
+ const txtFile = path.join(TEST_DIR, "test.txt");
183
+ fs.writeFileSync(txtFile, "content");
184
+ const formatters = await getFormattersForFile(txtFile, TEST_DIR);
185
+ expect(formatters).toEqual([]);
186
+ });
187
+ it("should cache detection results", async () => {
188
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
189
+ const tsFile = path.join(TEST_DIR, "test.ts");
190
+ // First call
191
+ await getFormattersForFile(tsFile, TEST_DIR);
192
+ // Second call should use cache
193
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
194
+ expect(formatters.map(f => f.name)).toContain("biome");
195
+ });
196
+ });
197
+ describe("clearFormatterCache()", () => {
198
+ it("should clear cached detection results", async () => {
199
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
200
+ const tsFile = path.join(TEST_DIR, "test.ts");
201
+ // First detection
202
+ await getFormattersForFile(tsFile, TEST_DIR);
203
+ // Clear cache
204
+ clearFormatterCache();
205
+ // Delete config
206
+ fs.rmSync(path.join(TEST_DIR, "biome.json"));
207
+ // Should re-detect (now without biome)
208
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
209
+ expect(formatters.map(f => f.name)).not.toContain("biome");
210
+ });
211
+ });
212
+ describe("formatFile()", () => {
213
+ it("should format file and report changes", async () => {
214
+ // Create a simple test - we'll skip actual formatter execution
215
+ // because we can't guarantee formatters are installed
216
+ const testFile = path.join(TEST_DIR, "test.txt");
217
+ fs.writeFileSync(testFile, "unchanged");
218
+ const mockFormatter = {
219
+ name: "mock",
220
+ command: ["echo", "$FILE"],
221
+ extensions: [".txt"],
222
+ detect: async () => true,
223
+ };
224
+ const result = await formatFile(testFile, mockFormatter);
225
+ // echo command should succeed but not change file
226
+ expect(result.success).toBe(true);
227
+ });
228
+ it("should handle formatter execution with valid command", async () => {
229
+ const testFile = path.join(TEST_DIR, "valid.txt");
230
+ fs.writeFileSync(testFile, "content");
231
+ // Use a valid command that succeeds but doesn't modify file
232
+ const mockFormatter = {
233
+ name: "valid",
234
+ command: process.platform === "win32" ? ["cmd", "/c", "echo", "$FILE"] : ["echo", "$FILE"],
235
+ extensions: [".txt"],
236
+ detect: async () => true,
237
+ };
238
+ const result = await formatFile(testFile, mockFormatter);
239
+ // Should not throw, completes with success
240
+ expect(result).toBeDefined();
241
+ expect(typeof result.success).toBe("boolean");
242
+ });
243
+ });
244
+ describe("Formatter extensions", () => {
245
+ it("biome should handle TS/JS/JSON/CSS/Vue/Svelte", () => {
246
+ expect(biomeFormatter.extensions).toContain(".ts");
247
+ expect(biomeFormatter.extensions).toContain(".tsx");
248
+ expect(biomeFormatter.extensions).toContain(".js");
249
+ expect(biomeFormatter.extensions).toContain(".json");
250
+ expect(biomeFormatter.extensions).toContain(".css");
251
+ expect(biomeFormatter.extensions).toContain(".vue");
252
+ expect(biomeFormatter.extensions).toContain(".svelte");
253
+ });
254
+ it("prettier should handle Markdown and YAML", () => {
255
+ expect(prettierFormatter.extensions).toContain(".md");
256
+ expect(prettierFormatter.extensions).toContain(".mdx");
257
+ expect(prettierFormatter.extensions).toContain(".yaml");
258
+ expect(prettierFormatter.extensions).toContain(".yml");
259
+ });
260
+ it("ruff should handle Python files", () => {
261
+ expect(ruffFormatter.extensions).toContain(".py");
262
+ expect(ruffFormatter.extensions).toContain(".pyi");
263
+ });
264
+ it("gofmt should handle Go files", () => {
265
+ expect(gofmtFormatter.extensions).toContain(".go");
266
+ });
267
+ it("rustfmt should handle Rust files", () => {
268
+ expect(rustfmtFormatter.extensions).toContain(".rs");
269
+ });
270
+ });
271
+ });
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Formatter Detection Tests
3
+ *
4
+ * Tests smart detection of formatters based on:
5
+ * - Config files (biome.json, .prettierrc, etc.)
6
+ * - Dependencies (package.json, requirements.txt)
7
+ * - Binary availability
8
+ */
9
+
10
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+ import {
14
+ biomeFormatter,
15
+ prettierFormatter,
16
+ ruffFormatter,
17
+ blackFormatter,
18
+ gofmtFormatter,
19
+ rustfmtFormatter,
20
+ getFormattersForFile,
21
+ clearFormatterCache,
22
+ formatFile,
23
+ } from "../formatters.js";
24
+ import { fileURLToPath } from "url";
25
+ import { dirname } from "path";
26
+
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = dirname(__filename);
29
+
30
+ const TEST_DIR = path.join(__dirname, "..", "..", "test-formatters");
31
+
32
+ describe("Formatter Detection", () => {
33
+ beforeEach(() => {
34
+ clearFormatterCache();
35
+ if (fs.existsSync(TEST_DIR)) {
36
+ fs.rmSync(TEST_DIR, { recursive: true });
37
+ }
38
+ fs.mkdirSync(TEST_DIR, { recursive: true });
39
+ });
40
+
41
+ afterEach(() => {
42
+ clearFormatterCache();
43
+ if (fs.existsSync(TEST_DIR)) {
44
+ fs.rmSync(TEST_DIR, { recursive: true });
45
+ }
46
+ });
47
+
48
+ describe("biomeFormatter.detect()", () => {
49
+ it("should detect biome.json config file", async () => {
50
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), '{"formatter": {}}');
51
+
52
+ const detected = await biomeFormatter.detect(TEST_DIR);
53
+
54
+ expect(detected).toBe(true);
55
+ });
56
+
57
+ it("should detect biome.jsonc config file", async () => {
58
+ fs.writeFileSync(path.join(TEST_DIR, "biome.jsonc"), "{}");
59
+
60
+ const detected = await biomeFormatter.detect(TEST_DIR);
61
+
62
+ expect(detected).toBe(true);
63
+ });
64
+
65
+ it("should detect @biomejs/biome in devDependencies", async () => {
66
+ fs.writeFileSync(
67
+ path.join(TEST_DIR, "package.json"),
68
+ JSON.stringify({ devDependencies: { "@biomejs/biome": "^1.0.0" } })
69
+ );
70
+
71
+ const detected = await biomeFormatter.detect(TEST_DIR);
72
+
73
+ expect(detected).toBe(true);
74
+ });
75
+
76
+ it("should return false when no biome config", async () => {
77
+ const detected = await biomeFormatter.detect(TEST_DIR);
78
+ expect(detected).toBe(false);
79
+ });
80
+
81
+ it("should find biome.json in parent directory", async () => {
82
+ const subDir = path.join(TEST_DIR, "src", "components");
83
+ fs.mkdirSync(subDir, { recursive: true });
84
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
85
+
86
+ const detected = await biomeFormatter.detect(subDir);
87
+
88
+ expect(detected).toBe(true);
89
+ });
90
+ });
91
+
92
+ describe("prettierFormatter.detect()", () => {
93
+ it("should detect .prettierrc config file", async () => {
94
+ fs.writeFileSync(path.join(TEST_DIR, ".prettierrc"), "{}");
95
+
96
+ const detected = await prettierFormatter.detect(TEST_DIR);
97
+
98
+ expect(detected).toBe(true);
99
+ });
100
+
101
+ it("should detect prettier in devDependencies", async () => {
102
+ fs.writeFileSync(
103
+ path.join(TEST_DIR, "package.json"),
104
+ JSON.stringify({ devDependencies: { prettier: "^3.0.0" } })
105
+ );
106
+
107
+ const detected = await prettierFormatter.detect(TEST_DIR);
108
+
109
+ expect(detected).toBe(true);
110
+ });
111
+
112
+ it("should detect prettier field in package.json", async () => {
113
+ fs.writeFileSync(
114
+ path.join(TEST_DIR, "package.json"),
115
+ JSON.stringify({ prettier: { semi: false } })
116
+ );
117
+
118
+ const detected = await prettierFormatter.detect(TEST_DIR);
119
+
120
+ expect(detected).toBe(true);
121
+ });
122
+
123
+ it("should return false when no prettier config", async () => {
124
+ const detected = await prettierFormatter.detect(TEST_DIR);
125
+ expect(detected).toBe(false);
126
+ });
127
+ });
128
+
129
+ describe("ruffFormatter.detect()", () => {
130
+ it("should detect [tool.ruff] in pyproject.toml", async () => {
131
+ fs.writeFileSync(
132
+ path.join(TEST_DIR, "pyproject.toml"),
133
+ "[tool.ruff]\nline-length = 100"
134
+ );
135
+
136
+ const detected = await ruffFormatter.detect(TEST_DIR);
137
+
138
+ expect(detected).toBe(true);
139
+ });
140
+
141
+ it("should detect ruff.toml config file", async () => {
142
+ fs.writeFileSync(path.join(TEST_DIR, "ruff.toml"), "line-length = 100");
143
+
144
+ const detected = await ruffFormatter.detect(TEST_DIR);
145
+
146
+ expect(detected).toBe(true);
147
+ });
148
+
149
+ it("should detect .ruff.toml config file", async () => {
150
+ fs.writeFileSync(path.join(TEST_DIR, ".ruff.toml"), "line-length = 100");
151
+
152
+ const detected = await ruffFormatter.detect(TEST_DIR);
153
+
154
+ expect(detected).toBe(true);
155
+ });
156
+
157
+ it("should detect ruff in requirements.txt", async () => {
158
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff==0.1.0\n");
159
+
160
+ const detected = await ruffFormatter.detect(TEST_DIR);
161
+
162
+ expect(detected).toBe(true);
163
+ });
164
+
165
+ it("should detect ruff even if [tool.black] exists (no preference logic)", async () => {
166
+ // Create pyproject.toml with black config
167
+ fs.writeFileSync(
168
+ path.join(TEST_DIR, "pyproject.toml"),
169
+ "[tool.black]\nline-length = 100"
170
+ );
171
+ // Also write requirements with ruff
172
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "ruff\n");
173
+
174
+ // The current implementation doesn't have preference logic
175
+ // Both black and ruff would be detected if their configs exist
176
+ // This is intentional - users can disable one if needed
177
+ const detected = await blackFormatter.detect(TEST_DIR);
178
+ expect(detected).toBe(true);
179
+ });
180
+ });
181
+
182
+ describe("blackFormatter.detect()", () => {
183
+ it("should detect [tool.black] in pyproject.toml", async () => {
184
+ fs.writeFileSync(
185
+ path.join(TEST_DIR, "pyproject.toml"),
186
+ "[tool.black]\nline-length = 100"
187
+ );
188
+
189
+ const detected = await blackFormatter.detect(TEST_DIR);
190
+
191
+ expect(detected).toBe(true);
192
+ });
193
+
194
+ it("should detect black in requirements.txt", async () => {
195
+ fs.writeFileSync(path.join(TEST_DIR, "requirements.txt"), "black==23.0.0\n");
196
+
197
+ const detected = await blackFormatter.detect(TEST_DIR);
198
+
199
+ expect(detected).toBe(true);
200
+ });
201
+
202
+ it("should return false when no black config", async () => {
203
+ const detected = await blackFormatter.detect(TEST_DIR);
204
+ expect(detected).toBe(false);
205
+ });
206
+ });
207
+
208
+ describe("gofmtFormatter.detect()", () => {
209
+ it("should detect gofmt binary availability", async () => {
210
+ // This test depends on whether gofmt is installed
211
+ // We can't reliably test this in CI, but we can verify the logic
212
+ const detected = await gofmtFormatter.detect(TEST_DIR);
213
+ // Should return boolean based on binary availability
214
+ expect(typeof detected).toBe("boolean");
215
+ });
216
+ });
217
+
218
+ describe("rustfmtFormatter.detect()", () => {
219
+ it("should detect rustfmt binary availability", async () => {
220
+ const detected = await rustfmtFormatter.detect(TEST_DIR);
221
+ expect(typeof detected).toBe("boolean");
222
+ });
223
+ });
224
+
225
+ describe("getFormattersForFile()", () => {
226
+ it("should return formatters for TypeScript file with biome config", async () => {
227
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
228
+ const tsFile = path.join(TEST_DIR, "test.ts");
229
+
230
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
231
+
232
+ expect(formatters.map(f => f.name)).toContain("biome");
233
+ });
234
+
235
+ it("should return formatters for TypeScript file with prettier", async () => {
236
+ fs.writeFileSync(
237
+ path.join(TEST_DIR, "package.json"),
238
+ JSON.stringify({ devDependencies: { prettier: "^3.0.0" } })
239
+ );
240
+ const tsFile = path.join(TEST_DIR, "test.ts");
241
+
242
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
243
+
244
+ expect(formatters.map(f => f.name)).toContain("prettier");
245
+ });
246
+
247
+ it("should return multiple formatters for TypeScript file", async () => {
248
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
249
+ fs.writeFileSync(
250
+ path.join(TEST_DIR, "package.json"),
251
+ JSON.stringify({ devDependencies: { prettier: "^3.0.0" } })
252
+ );
253
+ const tsFile = path.join(TEST_DIR, "test.ts");
254
+
255
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
256
+
257
+ // Both biome and prettier should be returned
258
+ expect(formatters.length).toBeGreaterThanOrEqual(1);
259
+ const names = formatters.map(f => f.name);
260
+ expect(names).toContain("biome");
261
+ });
262
+
263
+ it("should return ruff for Python file with pyproject.toml", async () => {
264
+ fs.writeFileSync(
265
+ path.join(TEST_DIR, "pyproject.toml"),
266
+ "[tool.ruff]\nline-length = 100"
267
+ );
268
+ const pyFile = path.join(TEST_DIR, "test.py");
269
+
270
+ const formatters = await getFormattersForFile(pyFile, TEST_DIR);
271
+
272
+ expect(formatters.map(f => f.name)).toContain("ruff");
273
+ });
274
+
275
+ it("should return black for Python file with black config", async () => {
276
+ fs.writeFileSync(
277
+ path.join(TEST_DIR, "pyproject.toml"),
278
+ "[tool.black]\nline-length = 100"
279
+ );
280
+ const pyFile = path.join(TEST_DIR, "test.py");
281
+
282
+ const formatters = await getFormattersForFile(pyFile, TEST_DIR);
283
+
284
+ // Should prefer black over ruff
285
+ expect(formatters.map(f => f.name)).toContain("black");
286
+ });
287
+
288
+ it("should return empty array for unsupported extensions", async () => {
289
+ const txtFile = path.join(TEST_DIR, "test.txt");
290
+ fs.writeFileSync(txtFile, "content");
291
+
292
+ const formatters = await getFormattersForFile(txtFile, TEST_DIR);
293
+
294
+ expect(formatters).toEqual([]);
295
+ });
296
+
297
+ it("should cache detection results", async () => {
298
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
299
+ const tsFile = path.join(TEST_DIR, "test.ts");
300
+
301
+ // First call
302
+ await getFormattersForFile(tsFile, TEST_DIR);
303
+ // Second call should use cache
304
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
305
+
306
+ expect(formatters.map(f => f.name)).toContain("biome");
307
+ });
308
+ });
309
+
310
+ describe("clearFormatterCache()", () => {
311
+ it("should clear cached detection results", async () => {
312
+ fs.writeFileSync(path.join(TEST_DIR, "biome.json"), "{}");
313
+ const tsFile = path.join(TEST_DIR, "test.ts");
314
+
315
+ // First detection
316
+ await getFormattersForFile(tsFile, TEST_DIR);
317
+
318
+ // Clear cache
319
+ clearFormatterCache();
320
+
321
+ // Delete config
322
+ fs.rmSync(path.join(TEST_DIR, "biome.json"));
323
+
324
+ // Should re-detect (now without biome)
325
+ const formatters = await getFormattersForFile(tsFile, TEST_DIR);
326
+ expect(formatters.map(f => f.name)).not.toContain("biome");
327
+ });
328
+ });
329
+
330
+ describe("formatFile()", () => {
331
+ it("should format file and report changes", async () => {
332
+ // Create a simple test - we'll skip actual formatter execution
333
+ // because we can't guarantee formatters are installed
334
+ const testFile = path.join(TEST_DIR, "test.txt");
335
+ fs.writeFileSync(testFile, "unchanged");
336
+
337
+ const mockFormatter = {
338
+ name: "mock",
339
+ command: ["echo", "$FILE"],
340
+ extensions: [".txt"],
341
+ detect: async () => true,
342
+ };
343
+
344
+ const result = await formatFile(testFile, mockFormatter);
345
+
346
+ // echo command should succeed but not change file
347
+ expect(result.success).toBe(true);
348
+ });
349
+
350
+ it("should handle formatter execution with valid command", async () => {
351
+ const testFile = path.join(TEST_DIR, "valid.txt");
352
+ fs.writeFileSync(testFile, "content");
353
+
354
+ // Use a valid command that succeeds but doesn't modify file
355
+ const mockFormatter = {
356
+ name: "valid",
357
+ command: process.platform === "win32" ? ["cmd", "/c", "echo", "$FILE"] : ["echo", "$FILE"],
358
+ extensions: [".txt"],
359
+ detect: async () => true,
360
+ };
361
+
362
+ const result = await formatFile(testFile, mockFormatter);
363
+
364
+ // Should not throw, completes with success
365
+ expect(result).toBeDefined();
366
+ expect(typeof result.success).toBe("boolean");
367
+ });
368
+ });
369
+
370
+ describe("Formatter extensions", () => {
371
+ it("biome should handle TS/JS/JSON/CSS/Vue/Svelte", () => {
372
+ expect(biomeFormatter.extensions).toContain(".ts");
373
+ expect(biomeFormatter.extensions).toContain(".tsx");
374
+ expect(biomeFormatter.extensions).toContain(".js");
375
+ expect(biomeFormatter.extensions).toContain(".json");
376
+ expect(biomeFormatter.extensions).toContain(".css");
377
+ expect(biomeFormatter.extensions).toContain(".vue");
378
+ expect(biomeFormatter.extensions).toContain(".svelte");
379
+ });
380
+
381
+ it("prettier should handle Markdown and YAML", () => {
382
+ expect(prettierFormatter.extensions).toContain(".md");
383
+ expect(prettierFormatter.extensions).toContain(".mdx");
384
+ expect(prettierFormatter.extensions).toContain(".yaml");
385
+ expect(prettierFormatter.extensions).toContain(".yml");
386
+ });
387
+
388
+ it("ruff should handle Python files", () => {
389
+ expect(ruffFormatter.extensions).toContain(".py");
390
+ expect(ruffFormatter.extensions).toContain(".pyi");
391
+ });
392
+
393
+ it("gofmt should handle Go files", () => {
394
+ expect(gofmtFormatter.extensions).toContain(".go");
395
+ });
396
+
397
+ it("rustfmt should handle Rust files", () => {
398
+ expect(rustfmtFormatter.extensions).toContain(".rs");
399
+ });
400
+ });
401
+ });