Merge pull request #3178 from code-yeongyu/fix/issue-3164
fix(jsonc): strip UTF-8 BOM before parsing JSONC files (#3164)
This commit is contained in:
@@ -140,10 +140,9 @@ describe("parseJsonc", () => {
|
||||
expect(() => parseJsonc(invalid)).toThrow()
|
||||
})
|
||||
|
||||
test("parses JSONC with UTF-8 BOM (Windows BOM files)", () => {
|
||||
// given - JSON with UTF-8 BOM marker
|
||||
const bom = "\uFEFF"
|
||||
const jsonc = `${bom}{ "key": "value" }`
|
||||
test("parses content with UTF-8 BOM prefix", () => {
|
||||
// given
|
||||
const jsonc = `\uFEFF{"key": "value"}`
|
||||
|
||||
// when
|
||||
const result = parseJsonc<{ key: string }>(jsonc)
|
||||
@@ -152,19 +151,20 @@ describe("parseJsonc", () => {
|
||||
expect(result.key).toBe("value")
|
||||
})
|
||||
|
||||
test("parses JSONC with BOM and comments", () => {
|
||||
// given - JSONC with UTF-8 BOM and comments
|
||||
const bom = "\uFEFF"
|
||||
const jsonc = `${bom}{
|
||||
// Windows editor saved with BOM
|
||||
"key": "value"
|
||||
test("parses commented JSONC with UTF-8 BOM prefix", () => {
|
||||
// given
|
||||
const jsonc = `\uFEFF{
|
||||
// Windows-saved file with BOM
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["oh-my-openagent@3.15.3"],
|
||||
}`
|
||||
|
||||
// when
|
||||
const result = parseJsonc<{ key: string }>(jsonc)
|
||||
const result = parseJsonc<{ $schema: string; plugin: string[] }>(jsonc)
|
||||
|
||||
// then
|
||||
expect(result.key).toBe("value")
|
||||
expect(result.$schema).toBe("https://opencode.ai/config.json")
|
||||
expect(result.plugin).toEqual(["oh-my-openagent@3.15.3"])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -193,6 +193,19 @@ describe("parseJsoncSafe", () => {
|
||||
expect(result.data).toBeNull()
|
||||
expect(result.errors.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test("returns data when content has UTF-8 BOM prefix", () => {
|
||||
// given
|
||||
const jsonc = `\uFEFF{"key": "value"}`
|
||||
|
||||
// when
|
||||
const result = parseJsoncSafe<{ key: string }>(jsonc)
|
||||
|
||||
// then
|
||||
expect(result.errors).toHaveLength(0)
|
||||
expect(result.data).not.toBeNull()
|
||||
expect(result.data?.key).toBe("value")
|
||||
})
|
||||
})
|
||||
|
||||
describe("readJsoncFile", () => {
|
||||
@@ -242,6 +255,28 @@ describe("readJsoncFile", () => {
|
||||
|
||||
rmSync(testDir, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
test("reads JSONC file written with UTF-8 BOM (Windows scenario)", () => {
|
||||
// given
|
||||
if (!existsSync(testDir)) mkdirSync(testDir, { recursive: true })
|
||||
const bomBytes = Buffer.from([0xef, 0xbb, 0xbf])
|
||||
const jsonBytes = Buffer.from(`{
|
||||
// Created on Windows with BOM
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["oh-my-openagent@3.15.3"]
|
||||
}`)
|
||||
writeFileSync(testFile, Buffer.concat([bomBytes, jsonBytes]))
|
||||
|
||||
// when
|
||||
const result = readJsoncFile<{ $schema: string; plugin: string[] }>(testFile)
|
||||
|
||||
// then
|
||||
expect(result).not.toBeNull()
|
||||
expect(result?.$schema).toBe("https://opencode.ai/config.json")
|
||||
expect(result?.plugin).toEqual(["oh-my-openagent@3.15.3"])
|
||||
|
||||
rmSync(testDir, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
|
||||
describe("detectConfigFile", () => {
|
||||
|
||||
@@ -9,12 +9,16 @@ export interface JsoncParseResult<T> {
|
||||
errors: Array<{ message: string; offset: number; length: number }>
|
||||
}
|
||||
|
||||
function stripBom(content: string): string {
|
||||
return content.charCodeAt(0) === 0xfeff ? content.slice(1) : content
|
||||
}
|
||||
|
||||
export function parseJsonc<T = unknown>(content: string): T {
|
||||
// Strip UTF-8 BOM if present (Windows UTF-8 with BOM files)
|
||||
content = content.replace(/^\uFEFF/, "")
|
||||
|
||||
const errors: ParseError[] = []
|
||||
const result = parse(content, errors, {
|
||||
const result = parse(stripBom(content), errors, {
|
||||
allowTrailingComma: true,
|
||||
disallowComments: false,
|
||||
}) as T
|
||||
@@ -31,7 +35,7 @@ export function parseJsonc<T = unknown>(content: string): T {
|
||||
|
||||
export function parseJsoncSafe<T = unknown>(content: string): JsoncParseResult<T> {
|
||||
const errors: ParseError[] = []
|
||||
const data = parse(content, errors, {
|
||||
const data = parse(stripBom(content), errors, {
|
||||
allowTrailingComma: true,
|
||||
disallowComments: false,
|
||||
}) as T | null
|
||||
|
||||
Reference in New Issue
Block a user