mirror of
https://github.com/aphrody-labs/oxc.git
synced 2026-10-14 22:29:17 +00:00
fix(codegen): reject invalid indent options (#25807)
## Summary - throw a TypeError when indent is empty, contains characters other than spaces or tabs, or is not a string - preserve the existing default tab indentation and unchanged printIndent hot path - update API documentation and validation coverage
This commit is contained in:
1 parent
f6b22654d2
commit
4cc7ea4595
4 files changed
+31
-34
No files matched your search
@@ -44,7 +44,8 @@ export interface Position {
|
||||
export interface Options {
|
||||
/**
|
||||
* String to use for indentation, defaults to `"\t"`.
|
||||
* Must consist only of spaces and/or tabs - anything else falls back to a tab.
|
||||
* Must be a non-empty string consisting only of spaces and/or tabs.
|
||||
* Throws a `TypeError` otherwise.
|
||||
*/
|
||||
indent?: string;
|
||||
|
||||
|
||||
@@ -28,10 +28,7 @@ let indentString = "\t";
|
||||
*/
|
||||
const indents = [""];
|
||||
|
||||
/**
|
||||
* The `indent` option must be made up of only spaces and tabs, and not empty string.
|
||||
* Anything else falls back to a tab.
|
||||
*/
|
||||
/** The `indent` option must be a non-empty string made up of only spaces and tabs. */
|
||||
const INDENT_REGEX = /^[ \t]+$/;
|
||||
|
||||
/** Upper bound for the process-wide indentation cache. */
|
||||
@@ -105,8 +102,13 @@ export class State {
|
||||
// The `indent` option is validated here, not in the printer, and changing of it discards
|
||||
// the cache grown for the old `indentString`.
|
||||
// That should be rare - most users have an indent style they prefer, and use it consistently.
|
||||
let { indent } = options;
|
||||
if (typeof indent !== "string" || !INDENT_REGEX.test(indent)) indent = "\t";
|
||||
const { indent: indentOption } = options;
|
||||
let indent = indentOption;
|
||||
if (indent === undefined) {
|
||||
indent = "\t";
|
||||
} else if (typeof indent !== "string" || !INDENT_REGEX.test(indent)) {
|
||||
throw new TypeError("`indent` must be a non-empty string containing only spaces and tabs");
|
||||
}
|
||||
if (indent !== indentString) {
|
||||
indentString = indent;
|
||||
indents.length = 1;
|
||||
|
||||
@@ -107,6 +107,22 @@ function checkCases(cases: Case[]): void {
|
||||
|
||||
// --- Tests ----------------------------------------------------------------------------------
|
||||
|
||||
describe("indent", () => {
|
||||
const ast = e(id("x"));
|
||||
|
||||
test.each(["", "x", "\n", "\r\n", " x "])("rejects %j", (indent) => {
|
||||
expect(() => printSync(ast, { indent })).toThrowError(
|
||||
new TypeError("`indent` must be a non-empty string containing only spaces and tabs"),
|
||||
);
|
||||
});
|
||||
|
||||
test.each([4, null, {}])("rejects non-string value %j", (indent) => {
|
||||
expect(() => printSync(ast, { indent: indent as unknown as string })).toThrowError(
|
||||
new TypeError("`indent` must be a non-empty string containing only spaces and tabs"),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("starting indent level", () => {
|
||||
const ast = e(id("x"));
|
||||
|
||||
|
||||
@@ -329,31 +329,9 @@ for (const fixture of FIXTURES) {
|
||||
// --- Indentation ----------------------------------------------------------------------------
|
||||
|
||||
// `printIndent` goes through the same write path as everything else, so the `indent` option and
|
||||
// the mappings have to agree: indentation is honoured (or falls back to a tab), and no mapping
|
||||
// ever points into the middle of an indent run.
|
||||
//
|
||||
// The invalid values are typed `unknown` because they are what an untyped JS caller can pass -
|
||||
// that they fall back to a tab rather than corrupting the output is the point of testing them.
|
||||
const INDENT_CASES: unknown[] = [
|
||||
// Valid - used as given
|
||||
undefined,
|
||||
"\t",
|
||||
" ",
|
||||
" ",
|
||||
"\t\t",
|
||||
" \t",
|
||||
"\t ",
|
||||
// Invalid - must fall back to a tab
|
||||
"",
|
||||
"x",
|
||||
"\n",
|
||||
"\r\n",
|
||||
" x ",
|
||||
" ",
|
||||
4,
|
||||
null,
|
||||
{},
|
||||
];
|
||||
// the mappings have to agree: indentation is honoured, and no mapping ever points into the middle
|
||||
// of an indent run.
|
||||
const INDENT_CASES = [undefined, "\t", " ", " ", "\t\t", " \t", "\t "];
|
||||
|
||||
describe("indent option", () => {
|
||||
test.each(
|
||||
@@ -362,11 +340,11 @@ describe("indent option", () => {
|
||||
const program = parseWithLocs("indent.js", INLINE_JS);
|
||||
const collector = new Collector("indent.js");
|
||||
const out = printSync(program, {
|
||||
indent: indent as string | undefined,
|
||||
indent,
|
||||
sourceMap: collector,
|
||||
}).code;
|
||||
|
||||
const expectedIndent = typeof indent === "string" && /^[ \t]+$/.test(indent) ? indent : "\t";
|
||||
const expectedIndent = indent ?? "\t";
|
||||
const lines = out.split("\n");
|
||||
const indented = lines.filter((line) => line.startsWith("\t") || line.startsWith(" "));
|
||||
expect(indented.length).toBeGreaterThan(0);
|
||||
|
||||
Reference in new issue
Block a user