format.test.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import { NodeFileSystem } from "@effect/platform-node"
  2. import { describe, expect } from "bun:test"
  3. import { Effect, Layer } from "effect"
  4. import { provideTmpdirInstance } from "../fixture/fixture"
  5. import { testEffect } from "../lib/effect"
  6. import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
  7. import { Format } from "../../src/format"
  8. import * as Formatter from "../../src/format/formatter"
  9. const it = testEffect(Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer))
  10. describe("Format", () => {
  11. it.live("status() returns built-in formatters when no config overrides", () =>
  12. provideTmpdirInstance(() =>
  13. Format.Service.use((fmt) =>
  14. Effect.gen(function* () {
  15. const statuses = yield* fmt.status()
  16. expect(Array.isArray(statuses)).toBe(true)
  17. expect(statuses.length).toBeGreaterThan(0)
  18. for (const item of statuses) {
  19. expect(typeof item.name).toBe("string")
  20. expect(Array.isArray(item.extensions)).toBe(true)
  21. expect(typeof item.enabled).toBe("boolean")
  22. }
  23. const gofmt = statuses.find((item) => item.name === "gofmt")
  24. expect(gofmt).toBeDefined()
  25. expect(gofmt!.extensions).toContain(".go")
  26. }),
  27. ),
  28. ),
  29. )
  30. it.live("status() returns empty list when formatter is disabled", () =>
  31. provideTmpdirInstance(
  32. () =>
  33. Format.Service.use((fmt) =>
  34. Effect.gen(function* () {
  35. expect(yield* fmt.status()).toEqual([])
  36. }),
  37. ),
  38. { config: { formatter: false } },
  39. ),
  40. )
  41. it.live("status() excludes formatters marked as disabled in config", () =>
  42. provideTmpdirInstance(
  43. () =>
  44. Format.Service.use((fmt) =>
  45. Effect.gen(function* () {
  46. const statuses = yield* fmt.status()
  47. const gofmt = statuses.find((item) => item.name === "gofmt")
  48. expect(gofmt).toBeUndefined()
  49. }),
  50. ),
  51. {
  52. config: {
  53. formatter: {
  54. gofmt: { disabled: true },
  55. },
  56. },
  57. },
  58. ),
  59. )
  60. it.live("status() excludes uv when ruff is disabled", () =>
  61. provideTmpdirInstance(
  62. () =>
  63. Format.Service.use((fmt) =>
  64. Effect.gen(function* () {
  65. const statuses = yield* fmt.status()
  66. expect(statuses.find((item) => item.name === "ruff")).toBeUndefined()
  67. expect(statuses.find((item) => item.name === "uv")).toBeUndefined()
  68. }),
  69. ),
  70. {
  71. config: {
  72. formatter: {
  73. ruff: { disabled: true },
  74. },
  75. },
  76. },
  77. ),
  78. )
  79. it.live("status() excludes ruff when uv is disabled", () =>
  80. provideTmpdirInstance(
  81. () =>
  82. Format.Service.use((fmt) =>
  83. Effect.gen(function* () {
  84. const statuses = yield* fmt.status()
  85. expect(statuses.find((item) => item.name === "ruff")).toBeUndefined()
  86. expect(statuses.find((item) => item.name === "uv")).toBeUndefined()
  87. }),
  88. ),
  89. {
  90. config: {
  91. formatter: {
  92. uv: { disabled: true },
  93. },
  94. },
  95. },
  96. ),
  97. )
  98. it.live("service initializes without error", () => provideTmpdirInstance(() => Format.Service.use(() => Effect.void)))
  99. it.live("status() initializes formatter state per directory", () =>
  100. Effect.gen(function* () {
  101. const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
  102. config: { formatter: false },
  103. })
  104. const b = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()))
  105. expect(a).toEqual([])
  106. expect(b.length).toBeGreaterThan(0)
  107. }),
  108. )
  109. it.live("runs enabled checks for matching formatters in parallel", () =>
  110. provideTmpdirInstance((path) =>
  111. Effect.gen(function* () {
  112. const file = `${path}/test.parallel`
  113. yield* Effect.promise(() => Bun.write(file, "x"))
  114. const one = {
  115. extensions: Formatter.gofmt.extensions,
  116. enabled: Formatter.gofmt.enabled,
  117. }
  118. const two = {
  119. extensions: Formatter.mix.extensions,
  120. enabled: Formatter.mix.enabled,
  121. }
  122. let active = 0
  123. let max = 0
  124. yield* Effect.acquireUseRelease(
  125. Effect.sync(() => {
  126. Formatter.gofmt.extensions = [".parallel"]
  127. Formatter.mix.extensions = [".parallel"]
  128. Formatter.gofmt.enabled = async () => {
  129. active++
  130. max = Math.max(max, active)
  131. await Bun.sleep(20)
  132. active--
  133. return ["sh", "-c", "true"]
  134. }
  135. Formatter.mix.enabled = async () => {
  136. active++
  137. max = Math.max(max, active)
  138. await Bun.sleep(20)
  139. active--
  140. return ["sh", "-c", "true"]
  141. }
  142. }),
  143. () =>
  144. Format.Service.use((fmt) =>
  145. Effect.gen(function* () {
  146. yield* fmt.init()
  147. yield* fmt.file(file)
  148. }),
  149. ),
  150. () =>
  151. Effect.sync(() => {
  152. Formatter.gofmt.extensions = one.extensions
  153. Formatter.gofmt.enabled = one.enabled
  154. Formatter.mix.extensions = two.extensions
  155. Formatter.mix.enabled = two.enabled
  156. }),
  157. )
  158. expect(max).toBe(2)
  159. }),
  160. ),
  161. )
  162. it.live("runs matching formatters sequentially for the same file", () =>
  163. provideTmpdirInstance(
  164. (path) =>
  165. Effect.gen(function* () {
  166. const file = `${path}/test.seq`
  167. yield* Effect.promise(() => Bun.write(file, "x"))
  168. yield* Format.Service.use((fmt) =>
  169. Effect.gen(function* () {
  170. yield* fmt.init()
  171. yield* fmt.file(file)
  172. }),
  173. )
  174. expect(yield* Effect.promise(() => Bun.file(file).text())).toBe("xAB")
  175. }),
  176. {
  177. config: {
  178. formatter: {
  179. first: {
  180. command: ["sh", "-c", 'sleep 0.05; v=$(cat "$1"); printf \'%sA\' "$v" > "$1"', "sh", "$FILE"],
  181. extensions: [".seq"],
  182. },
  183. second: {
  184. command: ["sh", "-c", 'v=$(cat "$1"); printf \'%sB\' "$v" > "$1"', "sh", "$FILE"],
  185. extensions: [".seq"],
  186. },
  187. },
  188. },
  189. },
  190. ),
  191. )
  192. })