Support configuration files with UTF-8 BOM (#20127)
This commit is contained in:
1 parent
6bc99db278
commit
1a36a7defd
5 files changed
+79
-10
No files matched your search
@@ -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.
|
||||||
|
```
|
||||||
@@ -2,10 +2,12 @@ import { pathToFileURL } from "node:url";
|
|||||||
import { parse as parseJson5 } from "@bybrave/json5";
|
import { parse as parseJson5 } from "@bybrave/json5";
|
||||||
import parseJson from "parse-json";
|
import parseJson from "parse-json";
|
||||||
import { parse as parseToml } from "smol-toml";
|
import { parse as parseToml } from "smol-toml";
|
||||||
|
import { stripBom } from "../../utilities/bom.js";
|
||||||
import readFile from "../../utilities/read-file.js";
|
import readFile from "../../utilities/read-file.js";
|
||||||
|
|
||||||
async function readJson(file) {
|
async function readJson(file) {
|
||||||
const content = await readFile(file);
|
let content = await readFile(file);
|
||||||
|
content = stripBom(content);
|
||||||
try {
|
try {
|
||||||
return parseJson(content);
|
return parseJson(content);
|
||||||
} catch (/** @type {any} */ error) {
|
} catch (/** @type {any} */ error) {
|
||||||
@@ -70,7 +72,8 @@ async function loadYaml(file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadToml(file) {
|
async function loadToml(file) {
|
||||||
const content = await readFile(file);
|
let content = await readFile(file);
|
||||||
|
content = stripBom(content);
|
||||||
try {
|
try {
|
||||||
return parseToml(content);
|
return parseToml(content);
|
||||||
} catch (/** @type {any} */ error) {
|
} catch (/** @type {any} */ error) {
|
||||||
|
|||||||
+7
-8
@@ -12,6 +12,7 @@ import {
|
|||||||
printDocToDebug,
|
printDocToDebug,
|
||||||
printDocToString as printDocToStringWithoutNormalizeOptions,
|
printDocToString as printDocToStringWithoutNormalizeOptions,
|
||||||
} from "../document/index.js";
|
} from "../document/index.js";
|
||||||
|
import { addBom, hasBom as stringHasBom } from "../utilities/bom.js";
|
||||||
import getAlignmentSize from "../utilities/get-alignment-size.js";
|
import getAlignmentSize from "../utilities/get-alignment-size.js";
|
||||||
import { prepareToPrint, printAstToDoc } from "./ast-to-doc.js";
|
import { prepareToPrint, printAstToDoc } from "./ast-to-doc.js";
|
||||||
import getCursorLocation from "./get-cursor-node.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 { resolveParser } from "./parser-and-printer.js";
|
||||||
import { calculateRange } from "./range.js";
|
import { calculateRange } from "./range.js";
|
||||||
|
|
||||||
const BOM = "\uFEFF";
|
|
||||||
|
|
||||||
const CURSOR = Symbol("cursor");
|
const CURSOR = Symbol("cursor");
|
||||||
|
|
||||||
async function coreFormat(originalText, opts, addAlignmentSize = 0) {
|
async function coreFormat(originalText, opts, addAlignmentSize = 0) {
|
||||||
@@ -271,9 +270,9 @@ function normalizeInputAndOptions(text, options) {
|
|||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasBOM = text.charAt(0) === BOM;
|
const hasBom = stringHasBom(text);
|
||||||
|
|
||||||
if (hasBOM) {
|
if (hasBom) {
|
||||||
text = text.slice(1);
|
text = text.slice(1);
|
||||||
cursorOffset--;
|
cursorOffset--;
|
||||||
rangeStart--;
|
rangeStart--;
|
||||||
@@ -297,7 +296,7 @@ function normalizeInputAndOptions(text, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasBOM,
|
hasBom,
|
||||||
text,
|
text,
|
||||||
options: normalizeIndexes(text, {
|
options: normalizeIndexes(text, {
|
||||||
...options,
|
...options,
|
||||||
@@ -320,7 +319,7 @@ async function hasIgnorePragma(text, options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function formatWithCursor(originalText, originalOptions) {
|
async function formatWithCursor(originalText, originalOptions) {
|
||||||
let { hasBOM, text, options } = normalizeInputAndOptions(
|
let { hasBom, text, options } = normalizeInputAndOptions(
|
||||||
originalText,
|
originalText,
|
||||||
await normalizeFormatOptions(originalOptions),
|
await normalizeFormatOptions(originalOptions),
|
||||||
);
|
);
|
||||||
@@ -353,8 +352,8 @@ async function formatWithCursor(originalText, originalOptions) {
|
|||||||
result = await coreFormat(text, options);
|
result = await coreFormat(text, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasBOM) {
|
if (hasBom) {
|
||||||
result.formatted = BOM + result.formatted;
|
result.formatted = addBom(result.formatted);
|
||||||
|
|
||||||
if (result.cursorOffset >= 0) {
|
if (result.cursorOffset >= 0) {
|
||||||
result.cursorOffset++;
|
result.cursorOffset++;
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -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,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in new issue
Block a user