Support configuration files with UTF-8 BOM (#20127)

This commit is contained in:
fisker Cheung authored and GitHub committed 2026-09-22 00:42:47 +08:00
1 parent 6bc99db278
commit 1a36a7defd
5 files changed
+79 -10

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
#### Support configuration files with UTF-8 BOM (#20127 by @fisker)
<!-- prettier-ignore -->
```console
# Input
$ cat .prettierrc.json
{"semi":false}
$ prettier example.js --check
# Prettier stable
Invalid configuration for file "example.js":
JSON Error in .prettierrc.json:
Unexpected token "" (\u{feff})
# Prettier main
Checking formatting...
[warn] example.js
[warn] Code style issues found in the above file. Run Prettier with --write to fix.
```
+5 -2
View File
@@ -2,10 +2,12 @@ import { pathToFileURL } from "node:url";
import { parse as parseJson5 } from "@bybrave/json5";
import parseJson from "parse-json";
import { parse as parseToml } from "smol-toml";
import { stripBom } from "../../utilities/bom.js";
import readFile from "../../utilities/read-file.js";
async function readJson(file) {
const content = await readFile(file);
let content = await readFile(file);
content = stripBom(content);
try {
return parseJson(content);
} catch (/** @type {any} */ error) {
@@ -70,7 +72,8 @@ async function loadYaml(file) {
}
async function loadToml(file) {
const content = await readFile(file);
let content = await readFile(file);
content = stripBom(content);
try {
return parseToml(content);
} catch (/** @type {any} */ error) {
+7 -8
View File
@@ -12,6 +12,7 @@ import {
printDocToDebug,
printDocToString as printDocToStringWithoutNormalizeOptions,
} from "../document/index.js";
import { addBom, hasBom as stringHasBom } from "../utilities/bom.js";
import getAlignmentSize from "../utilities/get-alignment-size.js";
import { prepareToPrint, printAstToDoc } from "./ast-to-doc.js";
import getCursorLocation from "./get-cursor-node.js";
@@ -21,8 +22,6 @@ import parseText from "./parse.js";
import { resolveParser } from "./parser-and-printer.js";
import { calculateRange } from "./range.js";
const BOM = "\uFEFF";
const CURSOR = Symbol("cursor");
async function coreFormat(originalText, opts, addAlignmentSize = 0) {
@@ -271,9 +270,9 @@ function normalizeInputAndOptions(text, options) {
options,
);
const hasBOM = text.charAt(0) === BOM;
const hasBom = stringHasBom(text);
if (hasBOM) {
if (hasBom) {
text = text.slice(1);
cursorOffset--;
rangeStart--;
@@ -297,7 +296,7 @@ function normalizeInputAndOptions(text, options) {
}
return {
hasBOM,
hasBom,
text,
options: normalizeIndexes(text, {
...options,
@@ -320,7 +319,7 @@ async function hasIgnorePragma(text, options) {
}
async function formatWithCursor(originalText, originalOptions) {
let { hasBOM, text, options } = normalizeInputAndOptions(
let { hasBom, text, options } = normalizeInputAndOptions(
originalText,
await normalizeFormatOptions(originalOptions),
);
@@ -353,8 +352,8 @@ async function formatWithCursor(originalText, originalOptions) {
result = await coreFormat(text, options);
}
if (hasBOM) {
result.formatted = BOM + result.formatted;
if (hasBom) {
result.formatted = addBom(result.formatted);
if (result.cursorOffset >= 0) {
result.cursorOffset++;
+12
View File
@@ -0,0 +1,12 @@
const BOM = "\uFEFF";
/** @type {(string: string) => boolean} */
const hasBom = (string) => string.charAt(0) === BOM;
/** @type {(string: string) => string} */
const stripBom = (string) => (hasBom(string) ? string.slice(1) : string);
/** @type {(string: string) => string} */
const addBom = (string) => BOM + string;
export { addBom, hasBom, stripBom };
+36
View File
@@ -0,0 +1,36 @@
import fs from "node:fs/promises";
import path from "node:path";
import { temporaryDirectory as getTemporaryDirectory } from "tempy";
import loadConfig from "../../src/config/prettier-config/load-config.js";
describe("Support BOM", () => {
const files = [
{ filename: "package.json", content: '{"prettier": {"tabWidth": 5}}' },
{ filename: ".prettierrc", content: "tabWidth: 5" },
{ filename: ".prettierrc.json", content: '{"tabWidth": 5}' },
{ filename: ".prettierrc.yml", content: "tabWidth: 5" },
{ filename: ".prettierrc.json5", content: "{tabWidth: 5}" },
{ filename: ".prettierrc.toml", content: "tabWidth = 5" },
];
const directory = getTemporaryDirectory();
const expected = { tabWidth: 5 };
beforeAll(async () => {
await Promise.all(
files.map(({ filename, content }) =>
fs.writeFile(path.join(directory, filename), `\ufeff${content}`),
),
);
});
afterAll(async () => {
await fs.rm(directory, { recursive: true, force: true });
});
for (const { filename } of files) {
test(filename, async () => {
expect(await loadConfig(path.join(directory, filename))).toStrictEqual(
expected,
);
});
}
});