The cross-spawn-spawner module has been moved from src/effect/ to src/ to simplify the core package structure. The src/types.d.ts file which contained unused type declarations has also been removed. All imports throughout the codebase have been updated to reflect the new location. This change reduces the package's internal complexity by flattening the module hierarchy and removing dead code, making future maintenance easier.
273 lines
7.8 KiB
TypeScript
273 lines
7.8 KiB
TypeScript
import { NodeFileSystem } from "@effect/platform-node"
|
|
import { describe, expect } from "bun:test"
|
|
import { Effect, Layer } from "effect"
|
|
import { provideTmpdirInstance } from "../fixture/fixture"
|
|
import { testEffect } from "../lib/effect"
|
|
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
|
import { Format } from "../../src/format"
|
|
import * as Formatter from "../../src/format/formatter"
|
|
|
|
const it = testEffect(Layer.mergeAll(Format.defaultLayer, CrossSpawnSpawner.defaultLayer, NodeFileSystem.layer))
|
|
|
|
describe("Format", () => {
|
|
it.live("status() returns empty list when no formatters are configured", () =>
|
|
provideTmpdirInstance(() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
expect(yield* fmt.status()).toEqual([])
|
|
}),
|
|
),
|
|
),
|
|
)
|
|
|
|
it.live("status() returns built-in formatters when formatter is true", () =>
|
|
provideTmpdirInstance(
|
|
() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
const statuses = yield* fmt.status()
|
|
const gofmt = statuses.find((item) => item.name === "gofmt")
|
|
expect(gofmt).toBeDefined()
|
|
expect(gofmt!.extensions).toContain(".go")
|
|
}),
|
|
),
|
|
{
|
|
config: {
|
|
formatter: true,
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("status() keeps built-in formatters when config object is provided", () =>
|
|
provideTmpdirInstance(
|
|
() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
const statuses = yield* fmt.status()
|
|
const gofmt = statuses.find((item) => item.name === "gofmt")
|
|
const mix = statuses.find((item) => item.name === "mix")
|
|
expect(gofmt).toBeDefined()
|
|
expect(gofmt!.extensions).toContain(".go")
|
|
expect(mix).toBeDefined()
|
|
}),
|
|
),
|
|
{
|
|
config: {
|
|
formatter: {
|
|
gofmt: {},
|
|
},
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("status() excludes formatters marked as disabled in config", () =>
|
|
provideTmpdirInstance(
|
|
() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
const statuses = yield* fmt.status()
|
|
const gofmt = statuses.find((item) => item.name === "gofmt")
|
|
const mix = statuses.find((item) => item.name === "mix")
|
|
expect(gofmt).toBeUndefined()
|
|
expect(mix).toBeDefined()
|
|
}),
|
|
),
|
|
{
|
|
config: {
|
|
formatter: {
|
|
gofmt: { disabled: true },
|
|
},
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("status() excludes uv when ruff is disabled", () =>
|
|
provideTmpdirInstance(
|
|
() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
const statuses = yield* fmt.status()
|
|
expect(statuses.find((item) => item.name === "ruff")).toBeUndefined()
|
|
expect(statuses.find((item) => item.name === "uv")).toBeUndefined()
|
|
}),
|
|
),
|
|
{
|
|
config: {
|
|
formatter: {
|
|
ruff: { disabled: true },
|
|
},
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("status() excludes ruff when uv is disabled", () =>
|
|
provideTmpdirInstance(
|
|
() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
const statuses = yield* fmt.status()
|
|
expect(statuses.find((item) => item.name === "ruff")).toBeUndefined()
|
|
expect(statuses.find((item) => item.name === "uv")).toBeUndefined()
|
|
}),
|
|
),
|
|
{
|
|
config: {
|
|
formatter: {
|
|
uv: { disabled: true },
|
|
},
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("service initializes without error", () => provideTmpdirInstance(() => Format.Service.use(() => Effect.void)))
|
|
|
|
it.live("file() returns false when no formatter runs", () =>
|
|
provideTmpdirInstance(
|
|
(dir) =>
|
|
Effect.gen(function* () {
|
|
const file = `${dir}/test.txt`
|
|
yield* Effect.promise(() => Bun.write(file, "x"))
|
|
|
|
const formatted = yield* Format.Service.use((fmt) => fmt.file(file))
|
|
expect(formatted).toBe(false)
|
|
}),
|
|
{
|
|
config: {
|
|
formatter: false,
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("status() initializes formatter state per directory", () =>
|
|
Effect.gen(function* () {
|
|
const a = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
|
|
config: { formatter: false },
|
|
})
|
|
const b = yield* provideTmpdirInstance(() => Format.Service.use((fmt) => fmt.status()), {
|
|
config: {
|
|
formatter: true,
|
|
},
|
|
})
|
|
|
|
expect(a).toEqual([])
|
|
expect(b.find((item) => item.name === "gofmt")).toBeDefined()
|
|
}),
|
|
)
|
|
|
|
it.live("runs enabled checks for matching formatters in parallel", () =>
|
|
provideTmpdirInstance(
|
|
(path) =>
|
|
Effect.gen(function* () {
|
|
const file = `${path}/test.parallel`
|
|
yield* Effect.promise(() => Bun.write(file, "x"))
|
|
|
|
const one = {
|
|
extensions: Formatter.gofmt.extensions,
|
|
enabled: Formatter.gofmt.enabled,
|
|
}
|
|
const two = {
|
|
extensions: Formatter.mix.extensions,
|
|
enabled: Formatter.mix.enabled,
|
|
}
|
|
|
|
let active = 0
|
|
let max = 0
|
|
|
|
yield* Effect.acquireUseRelease(
|
|
Effect.sync(() => {
|
|
Formatter.gofmt.extensions = [".parallel"]
|
|
Formatter.mix.extensions = [".parallel"]
|
|
Formatter.gofmt.enabled = async () => {
|
|
active++
|
|
max = Math.max(max, active)
|
|
await Bun.sleep(20)
|
|
active--
|
|
return ["sh", "-c", "true"]
|
|
}
|
|
Formatter.mix.enabled = async () => {
|
|
active++
|
|
max = Math.max(max, active)
|
|
await Bun.sleep(20)
|
|
active--
|
|
return ["sh", "-c", "true"]
|
|
}
|
|
}),
|
|
() =>
|
|
Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
yield* fmt.init()
|
|
yield* fmt.file(file)
|
|
}),
|
|
),
|
|
() =>
|
|
Effect.sync(() => {
|
|
Formatter.gofmt.extensions = one.extensions
|
|
Formatter.gofmt.enabled = one.enabled
|
|
Formatter.mix.extensions = two.extensions
|
|
Formatter.mix.enabled = two.enabled
|
|
}),
|
|
)
|
|
|
|
expect(max).toBe(2)
|
|
}),
|
|
{
|
|
config: {
|
|
formatter: {
|
|
gofmt: {},
|
|
mix: {},
|
|
},
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
it.live("runs matching formatters sequentially for the same file", () =>
|
|
provideTmpdirInstance(
|
|
(path) =>
|
|
Effect.gen(function* () {
|
|
const file = `${path}/test.seq`
|
|
yield* Effect.promise(() => Bun.write(file, "x"))
|
|
|
|
yield* Format.Service.use((fmt) =>
|
|
Effect.gen(function* () {
|
|
yield* fmt.init()
|
|
expect(yield* fmt.file(file)).toBe(true)
|
|
}),
|
|
)
|
|
|
|
expect(yield* Effect.promise(() => Bun.file(file).text())).toBe("xAB")
|
|
}),
|
|
{
|
|
config: {
|
|
formatter: {
|
|
first: {
|
|
command: [
|
|
"node",
|
|
"-e",
|
|
"const fs = require('fs'); const file = process.argv[1]; fs.writeFileSync(file, fs.readFileSync(file, 'utf8') + 'A')",
|
|
"$FILE",
|
|
],
|
|
extensions: [".seq"],
|
|
},
|
|
second: {
|
|
command: [
|
|
"node",
|
|
"-e",
|
|
"const fs = require('fs'); const file = process.argv[1]; fs.writeFileSync(file, fs.readFileSync(file, 'utf8') + 'B')",
|
|
"$FILE",
|
|
],
|
|
extensions: [".seq"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
),
|
|
)
|
|
})
|