2025-11-02 21:18:15 +08:00
|
|
|
import { convertEndOfLineToChars } from "../../common/end-of-line.js";
|
|
|
|
|
import getStringWidth from "../../utils/get-string-width.js";
|
2022-07-23 20:10:37 +08:00
|
|
|
import {
|
|
|
|
|
DOC_TYPE_ALIGN,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_ARRAY,
|
|
|
|
|
DOC_TYPE_BREAK_PARENT,
|
|
|
|
|
DOC_TYPE_CURSOR,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_FILL,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_GROUP,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_IF_BREAK,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_INDENT,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_INDENT_IF_BREAK,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_LABEL,
|
|
|
|
|
DOC_TYPE_LINE,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_LINE_SUFFIX,
|
|
|
|
|
DOC_TYPE_LINE_SUFFIX_BOUNDARY,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_STRING,
|
|
|
|
|
DOC_TYPE_TRIM,
|
2025-11-03 01:52:26 +08:00
|
|
|
hardlineWithoutBreakParent,
|
|
|
|
|
indent as indentDoc,
|
|
|
|
|
} from "../builders/index.js";
|
|
|
|
|
import { getDocType, propagateBreaks } from "../utilities/index.js";
|
|
|
|
|
import InvalidDocError from "../utilities/invalid-doc-error.js";
|
2025-11-02 21:18:15 +08:00
|
|
|
import { makeAlign, makeIndent, ROOT_INDENT } from "./indent.js";
|
2025-11-04 03:54:35 +08:00
|
|
|
import { trimIndentation } from "./trim-indentation.js";
|
2017-05-11 00:13:21 +01:00
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
/**
|
|
|
|
|
@import {EndOfLineOption} from "../../common/end-of-line.js";
|
2025-11-03 01:52:26 +08:00
|
|
|
@import {Doc} from "../builders/index.js";
|
2025-11-02 21:18:15 +08:00
|
|
|
@import {Indent, IndentOptions} from "./indent.js";
|
|
|
|
|
@typedef {typeof MODE_BREAK | typeof MODE_FLAT} Mode
|
|
|
|
|
@typedef {{ indent: Indent, doc: any, mode: Mode }} Command
|
|
|
|
|
@typedef {Record<symbol, Mode>} GroupModeMap
|
|
|
|
|
*/
|
2018-06-21 10:29:42 +08:00
|
|
|
|
2022-08-22 12:16:20 +08:00
|
|
|
/** @type {unique symbol} */
|
|
|
|
|
const MODE_BREAK = Symbol("MODE_BREAK");
|
|
|
|
|
/** @type {unique symbol} */
|
|
|
|
|
const MODE_FLAT = Symbol("MODE_FLAT");
|
2016-12-23 13:38:10 -05:00
|
|
|
|
2024-08-24 08:23:59 +09:00
|
|
|
const DOC_FILL_PRINTED_LENGTH = Symbol("DOC_FILL_PRINTED_LENGTH");
|
2024-07-26 19:51:00 +08:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
/**
|
|
|
|
|
* @param {Command} next
|
|
|
|
|
* @param {Command[]} restCommands
|
|
|
|
|
* @param {number} width
|
|
|
|
|
* @param {boolean} hasLineSuffix
|
2022-08-20 21:12:07 +08:00
|
|
|
* @param {GroupModeMap} groupModeMap
|
2022-07-23 20:45:25 +08:00
|
|
|
* @param {boolean} [mustBeFlat]
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
2022-08-20 21:12:07 +08:00
|
|
|
function fits(
|
|
|
|
|
next,
|
|
|
|
|
restCommands,
|
|
|
|
|
width,
|
|
|
|
|
hasLineSuffix,
|
|
|
|
|
groupModeMap,
|
|
|
|
|
mustBeFlat,
|
|
|
|
|
) {
|
2023-03-31 16:47:26 +08:00
|
|
|
if (width === Number.POSITIVE_INFINITY) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
let restCommandsIndex = restCommands.length;
|
|
|
|
|
/** @type {Array<Omit<Command, 'indent'>>} */
|
|
|
|
|
const commands = [next];
|
2018-10-06 14:18:52 +02:00
|
|
|
// `out` is only used for width counting because `trim` requires to look
|
|
|
|
|
// backwards for space characters.
|
|
|
|
|
const out = [];
|
2017-01-05 12:21:57 -05:00
|
|
|
while (width >= 0) {
|
2025-11-02 21:18:15 +08:00
|
|
|
if (commands.length === 0) {
|
|
|
|
|
if (restCommandsIndex === 0) {
|
2016-12-23 13:38:10 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(restCommands[--restCommandsIndex]);
|
2017-06-05 18:12:59 -04:00
|
|
|
|
|
|
|
|
continue;
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
const { mode, doc } = commands.pop();
|
2024-02-04 14:33:39 +08:00
|
|
|
const docType = getDocType(doc);
|
|
|
|
|
switch (docType) {
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_STRING:
|
2025-11-03 23:35:48 +08:00
|
|
|
if (doc) {
|
|
|
|
|
out.push(doc);
|
|
|
|
|
width -= getStringWidth(doc);
|
|
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-08-06 11:03:47 +08:00
|
|
|
case DOC_TYPE_ARRAY:
|
2022-07-23 20:45:25 +08:00
|
|
|
case DOC_TYPE_FILL: {
|
2024-02-04 14:33:39 +08:00
|
|
|
const parts = docType === DOC_TYPE_ARRAY ? doc : doc.parts;
|
2024-12-01 19:00:35 +09:00
|
|
|
const end = doc[DOC_FILL_PRINTED_LENGTH] ?? 0;
|
2025-11-02 21:18:15 +08:00
|
|
|
for (let index = parts.length - 1; index >= end; index--) {
|
|
|
|
|
commands.push({ mode, doc: parts[index] });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2021-01-04 10:04:30 +08:00
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_INDENT:
|
|
|
|
|
case DOC_TYPE_ALIGN:
|
2022-07-23 20:45:25 +08:00
|
|
|
case DOC_TYPE_INDENT_IF_BREAK:
|
|
|
|
|
case DOC_TYPE_LABEL:
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_TRIM:
|
2025-11-04 03:54:35 +08:00
|
|
|
width += trimIndentation(out).count;
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_GROUP: {
|
|
|
|
|
if (mustBeFlat && doc.break) {
|
|
|
|
|
return false;
|
2021-02-16 21:25:11 +02:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
const groupMode = doc.break ? MODE_BREAK : mode;
|
2022-07-23 20:45:25 +08:00
|
|
|
// The most expanded state takes up the least space on the current line.
|
|
|
|
|
const contents =
|
2022-07-23 20:10:37 +08:00
|
|
|
doc.expandedStates && groupMode === MODE_BREAK
|
2022-09-18 17:50:14 +08:00
|
|
|
? doc.expandedStates.at(-1)
|
2022-07-23 20:45:25 +08:00
|
|
|
: doc.contents;
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ mode: groupMode, doc: contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
|
|
|
|
}
|
2018-10-06 14:18:52 +02:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
case DOC_TYPE_IF_BREAK: {
|
2022-07-23 20:10:37 +08:00
|
|
|
const groupMode = doc.groupId
|
|
|
|
|
? groupModeMap[doc.groupId] || MODE_FLAT
|
|
|
|
|
: mode;
|
2022-07-23 20:45:25 +08:00
|
|
|
const contents =
|
|
|
|
|
groupMode === MODE_BREAK ? doc.breakContents : doc.flatContents;
|
|
|
|
|
if (contents) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ mode, doc: contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE:
|
2022-07-23 20:45:25 +08:00
|
|
|
if (mode === MODE_BREAK || doc.hard) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!doc.soft) {
|
|
|
|
|
out.push(" ");
|
|
|
|
|
width--;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE_SUFFIX:
|
|
|
|
|
hasLineSuffix = true;
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE_SUFFIX_BOUNDARY:
|
|
|
|
|
if (hasLineSuffix) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
/**
|
|
|
|
|
@param {Doc} doc
|
|
|
|
|
@param {{
|
|
|
|
|
printWidth: number,
|
|
|
|
|
endOfLine: EndOfLineOption,
|
|
|
|
|
} & IndentOptions} options
|
|
|
|
|
@returns
|
|
|
|
|
*/
|
2017-04-06 23:49:37 -03:00
|
|
|
function printDocToString(doc, options) {
|
2022-08-20 21:12:07 +08:00
|
|
|
/** @type GroupModeMap */
|
|
|
|
|
const groupModeMap = {};
|
2018-06-21 10:29:42 +08:00
|
|
|
|
2017-05-21 21:11:09 +02:00
|
|
|
const width = options.printWidth;
|
2018-11-06 14:47:13 +00:00
|
|
|
const newLine = convertEndOfLineToChars(options.endOfLine);
|
2025-11-02 21:18:15 +08:00
|
|
|
let position = 0;
|
|
|
|
|
// commands is basically a stack. We've turned a recursive call into a
|
2016-12-23 13:38:10 -05:00
|
|
|
// while loop which is much faster. The while loop below adds new
|
2025-11-02 21:18:15 +08:00
|
|
|
// commands to the array instead of recursively calling `print`.
|
2022-07-23 20:45:25 +08:00
|
|
|
/** @type Command[] */
|
2025-11-02 21:18:15 +08:00
|
|
|
const commands = [{ indent: ROOT_INDENT, mode: MODE_BREAK, doc }];
|
2025-11-04 03:54:35 +08:00
|
|
|
/** @type string[] */
|
|
|
|
|
let out = [];
|
2017-01-04 15:24:10 -05:00
|
|
|
let shouldRemeasure = false;
|
2022-07-23 20:45:25 +08:00
|
|
|
/** @type Command[] */
|
|
|
|
|
const lineSuffix = [];
|
2025-11-04 03:54:35 +08:00
|
|
|
const cursorPositions = [];
|
2017-01-26 14:57:43 -05:00
|
|
|
|
2023-03-07 20:42:14 +08:00
|
|
|
propagateBreaks(doc);
|
|
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
while (commands.length > 0) {
|
|
|
|
|
const { indent, mode, doc } = commands.pop();
|
2022-07-23 20:10:37 +08:00
|
|
|
switch (getDocType(doc)) {
|
|
|
|
|
case DOC_TYPE_STRING: {
|
2022-11-21 23:41:27 +08:00
|
|
|
const formatted =
|
|
|
|
|
newLine !== "\n" ? doc.replaceAll("\n", newLine) : doc;
|
2022-12-19 13:30:50 +08:00
|
|
|
// Plugins may print single string, should skip measure the width
|
2025-11-03 23:35:48 +08:00
|
|
|
if (formatted) {
|
|
|
|
|
out.push(formatted);
|
|
|
|
|
if (commands.length > 0) {
|
|
|
|
|
position += getStringWidth(formatted);
|
|
|
|
|
}
|
2022-12-19 13:30:50 +08:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2021-01-04 10:04:30 +08:00
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-10-08 10:44:30 +08:00
|
|
|
case DOC_TYPE_ARRAY:
|
2025-11-02 21:18:15 +08:00
|
|
|
for (let index = doc.length - 1; index >= 0; index--) {
|
|
|
|
|
commands.push({ indent, mode, doc: doc[index] });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2025-11-04 03:54:35 +08:00
|
|
|
case DOC_TYPE_CURSOR: {
|
|
|
|
|
if (cursorPositions.length >= 2) {
|
2022-08-26 21:33:49 +08:00
|
|
|
throw new Error("There are too many 'cursor' in doc.");
|
|
|
|
|
}
|
2025-11-04 03:54:35 +08:00
|
|
|
const text = out.join("");
|
|
|
|
|
out = [text];
|
|
|
|
|
cursorPositions.push(text.length - 1);
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2025-11-04 03:54:35 +08:00
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_INDENT:
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({
|
|
|
|
|
indent: makeIndent(indent, options),
|
|
|
|
|
mode,
|
|
|
|
|
doc: doc.contents,
|
|
|
|
|
});
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_ALIGN:
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({
|
|
|
|
|
indent: makeAlign(indent, doc.n, options),
|
2022-07-23 20:45:25 +08:00
|
|
|
mode,
|
|
|
|
|
doc: doc.contents,
|
|
|
|
|
});
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_TRIM:
|
2025-11-04 03:54:35 +08:00
|
|
|
trim();
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_GROUP:
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case MODE_FLAT:
|
|
|
|
|
if (!shouldRemeasure) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({
|
|
|
|
|
indent,
|
2022-07-23 20:45:25 +08:00
|
|
|
mode: doc.break ? MODE_BREAK : MODE_FLAT,
|
|
|
|
|
doc: doc.contents,
|
|
|
|
|
});
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// fallthrough
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case MODE_BREAK: {
|
|
|
|
|
shouldRemeasure = false;
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const next = { indent, mode: MODE_FLAT, doc: doc.contents };
|
|
|
|
|
const remainingWidth = width - position;
|
2022-07-23 20:10:37 +08:00
|
|
|
const hasLineSuffix = lineSuffix.length > 0;
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-08-20 21:12:07 +08:00
|
|
|
if (
|
|
|
|
|
!doc.break &&
|
2025-11-02 21:18:15 +08:00
|
|
|
fits(next, commands, remainingWidth, hasLineSuffix, groupModeMap)
|
2022-08-20 21:12:07 +08:00
|
|
|
) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(next);
|
2022-07-23 20:10:37 +08:00
|
|
|
} else {
|
|
|
|
|
// Expanded states are a rare case where a document
|
|
|
|
|
// can manually provide multiple representations of
|
|
|
|
|
// itself. It provides an array of documents
|
|
|
|
|
// going from the least expanded (most flattened)
|
|
|
|
|
// representation first to the most expanded. If a
|
|
|
|
|
// group has these, we need to manually go through
|
|
|
|
|
// these states and find the first one that fits.
|
2022-09-29 05:25:54 +03:00
|
|
|
// eslint-disable-next-line no-lonely-if
|
2022-07-23 20:10:37 +08:00
|
|
|
if (doc.expandedStates) {
|
2022-09-18 17:50:14 +08:00
|
|
|
const mostExpanded = doc.expandedStates.at(-1);
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
if (doc.break) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({
|
|
|
|
|
indent,
|
|
|
|
|
mode: MODE_BREAK,
|
|
|
|
|
doc: mostExpanded,
|
|
|
|
|
});
|
2022-07-23 20:10:37 +08:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
2025-11-02 21:18:15 +08:00
|
|
|
for (
|
|
|
|
|
let index = 1;
|
|
|
|
|
index < doc.expandedStates.length + 1;
|
|
|
|
|
index++
|
|
|
|
|
) {
|
|
|
|
|
if (index >= doc.expandedStates.length) {
|
|
|
|
|
commands.push({
|
|
|
|
|
indent,
|
|
|
|
|
mode: MODE_BREAK,
|
|
|
|
|
doc: mostExpanded,
|
|
|
|
|
});
|
2022-07-23 20:10:37 +08:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
2025-11-02 21:18:15 +08:00
|
|
|
const state = doc.expandedStates[index];
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const cmd = { indent, mode: MODE_FLAT, doc: state };
|
2022-07-23 20:10:37 +08:00
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
if (
|
|
|
|
|
fits(
|
|
|
|
|
cmd,
|
|
|
|
|
commands,
|
|
|
|
|
remainingWidth,
|
|
|
|
|
hasLineSuffix,
|
|
|
|
|
groupModeMap,
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
commands.push(cmd);
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2017-01-13 15:03:53 -05:00
|
|
|
break;
|
|
|
|
|
}
|
2017-01-04 15:24:10 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
} else {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ indent, mode: MODE_BREAK, doc: doc.contents });
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
2017-05-21 21:11:09 +02:00
|
|
|
}
|
2018-06-21 10:29:42 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2018-06-21 10:29:42 +08:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (doc.id) {
|
2025-11-02 21:18:15 +08:00
|
|
|
groupModeMap[doc.id] = commands.at(-1).mode;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// Fills each line with as much code as possible before moving to a new
|
|
|
|
|
// line with the same indentation.
|
|
|
|
|
//
|
|
|
|
|
// Expects doc.parts to be an array of alternating content and
|
|
|
|
|
// whitespace. The whitespace contains the linebreaks.
|
|
|
|
|
//
|
|
|
|
|
// For example:
|
|
|
|
|
// ["I", line, "love", line, "monkeys"]
|
|
|
|
|
// or
|
|
|
|
|
// [{ type: group, ... }, softline, { type: group, ... }]
|
|
|
|
|
//
|
|
|
|
|
// It uses this parts structure to handle three main layout cases:
|
|
|
|
|
// * The first two content items fit on the same line without
|
|
|
|
|
// breaking
|
|
|
|
|
// -> output the first content item and the whitespace "flat".
|
|
|
|
|
// * Only the first content item fits on the line without breaking
|
|
|
|
|
// -> output the first content item "flat" and the whitespace with
|
|
|
|
|
// "break".
|
|
|
|
|
// * Neither content item fits on the line without breaking
|
|
|
|
|
// -> output the first content item and the whitespace with "break".
|
|
|
|
|
case DOC_TYPE_FILL: {
|
2025-11-02 21:18:15 +08:00
|
|
|
const remainingWidth = width - position;
|
2022-07-23 20:10:37 +08:00
|
|
|
|
2024-08-24 08:23:59 +09:00
|
|
|
const offset = doc[DOC_FILL_PRINTED_LENGTH] ?? 0;
|
2022-07-23 20:10:37 +08:00
|
|
|
const { parts } = doc;
|
2024-08-24 08:23:59 +09:00
|
|
|
const length = parts.length - offset;
|
|
|
|
|
if (length === 0) {
|
2017-01-13 15:03:53 -05:00
|
|
|
break;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
2017-05-11 00:13:21 +01:00
|
|
|
|
2024-08-24 08:23:59 +09:00
|
|
|
const content = parts[offset + 0];
|
|
|
|
|
const whitespace = parts[offset + 1];
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const contentFlatCommand = { indent, mode: MODE_FLAT, doc: content };
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const contentBreakCommand = { indent, mode: MODE_BREAK, doc: content };
|
2022-07-23 20:10:37 +08:00
|
|
|
const contentFits = fits(
|
2025-11-02 21:18:15 +08:00
|
|
|
contentFlatCommand,
|
2022-07-23 20:10:37 +08:00
|
|
|
[],
|
2025-11-02 21:18:15 +08:00
|
|
|
remainingWidth,
|
2022-07-23 20:10:37 +08:00
|
|
|
lineSuffix.length > 0,
|
2022-08-20 21:12:07 +08:00
|
|
|
groupModeMap,
|
2022-07-23 20:10:37 +08:00
|
|
|
true,
|
|
|
|
|
);
|
2017-05-11 00:13:21 +01:00
|
|
|
|
2024-08-24 08:23:59 +09:00
|
|
|
if (length === 1) {
|
2022-07-23 20:10:37 +08:00
|
|
|
if (contentFits) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(contentFlatCommand);
|
2017-05-11 00:13:21 +01:00
|
|
|
} else {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(contentBreakCommand);
|
2017-05-11 00:13:21 +01:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-12-31 14:38:33 -05:00
|
|
|
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const whitespaceFlatCommand = {
|
|
|
|
|
indent,
|
|
|
|
|
mode: MODE_FLAT,
|
|
|
|
|
doc: whitespace,
|
|
|
|
|
};
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const whitespaceBreakCommand = {
|
|
|
|
|
indent,
|
|
|
|
|
mode: MODE_BREAK,
|
|
|
|
|
doc: whitespace,
|
|
|
|
|
};
|
2022-07-23 20:10:37 +08:00
|
|
|
|
2024-08-24 08:23:59 +09:00
|
|
|
if (length === 2) {
|
2022-07-23 20:10:37 +08:00
|
|
|
if (contentFits) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(whitespaceFlatCommand, contentFlatCommand);
|
2022-07-23 20:10:37 +08:00
|
|
|
} else {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(whitespaceBreakCommand, contentBreakCommand);
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
2017-01-26 14:57:43 -05:00
|
|
|
break;
|
2018-06-21 10:29:42 +08:00
|
|
|
}
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2024-08-24 08:23:59 +09:00
|
|
|
const secondContent = parts[offset + 2];
|
2024-07-26 19:51:00 +08:00
|
|
|
|
|
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const remainingCommand = {
|
|
|
|
|
indent,
|
2024-08-24 08:23:59 +09:00
|
|
|
mode,
|
|
|
|
|
doc: { ...doc, [DOC_FILL_PRINTED_LENGTH]: offset + 2 },
|
|
|
|
|
};
|
2017-05-21 21:11:09 +02:00
|
|
|
|
2024-04-08 14:26:54 +08:00
|
|
|
/** @type {Command} */
|
2025-11-02 21:18:15 +08:00
|
|
|
const firstAndSecondContentFlatCommand = {
|
|
|
|
|
indent,
|
2022-07-23 20:45:25 +08:00
|
|
|
mode: MODE_FLAT,
|
|
|
|
|
doc: [content, whitespace, secondContent],
|
|
|
|
|
};
|
2022-07-23 20:10:37 +08:00
|
|
|
const firstAndSecondContentFits = fits(
|
2025-11-02 21:18:15 +08:00
|
|
|
firstAndSecondContentFlatCommand,
|
2022-07-23 20:10:37 +08:00
|
|
|
[],
|
2025-11-02 21:18:15 +08:00
|
|
|
remainingWidth,
|
2022-07-23 20:10:37 +08:00
|
|
|
lineSuffix.length > 0,
|
2022-08-20 21:12:07 +08:00
|
|
|
groupModeMap,
|
2022-07-23 20:10:37 +08:00
|
|
|
true,
|
|
|
|
|
);
|
2017-02-13 07:13:16 -08:00
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(remainingCommand);
|
|
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
if (firstAndSecondContentFits) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(whitespaceFlatCommand, contentFlatCommand);
|
2022-07-23 20:10:37 +08:00
|
|
|
} else if (contentFits) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(whitespaceBreakCommand, contentFlatCommand);
|
2022-07-23 20:10:37 +08:00
|
|
|
} else {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push(whitespaceBreakCommand, contentBreakCommand);
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_IF_BREAK:
|
|
|
|
|
case DOC_TYPE_INDENT_IF_BREAK: {
|
|
|
|
|
const groupMode = doc.groupId ? groupModeMap[doc.groupId] : mode;
|
|
|
|
|
if (groupMode === MODE_BREAK) {
|
|
|
|
|
const breakContents =
|
|
|
|
|
doc.type === DOC_TYPE_IF_BREAK
|
|
|
|
|
? doc.breakContents
|
|
|
|
|
: doc.negate
|
|
|
|
|
? doc.contents
|
2025-11-02 21:18:15 +08:00
|
|
|
: indentDoc(doc.contents);
|
2022-07-23 20:10:37 +08:00
|
|
|
if (breakContents) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ indent, mode, doc: breakContents });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (groupMode === MODE_FLAT) {
|
|
|
|
|
const flatContents =
|
|
|
|
|
doc.type === DOC_TYPE_IF_BREAK
|
|
|
|
|
? doc.flatContents
|
|
|
|
|
: doc.negate
|
2025-11-02 21:18:15 +08:00
|
|
|
? indentDoc(doc.contents)
|
2022-07-23 20:10:37 +08:00
|
|
|
: doc.contents;
|
|
|
|
|
if (flatContents) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ indent, mode, doc: flatContents });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case DOC_TYPE_LINE_SUFFIX:
|
2025-11-02 21:18:15 +08:00
|
|
|
lineSuffix.push({ indent, mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE_SUFFIX_BOUNDARY:
|
|
|
|
|
if (lineSuffix.length > 0) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ indent, mode, doc: hardlineWithoutBreakParent });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE:
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case MODE_FLAT:
|
|
|
|
|
if (!doc.hard) {
|
|
|
|
|
if (!doc.soft) {
|
|
|
|
|
out.push(" ");
|
|
|
|
|
|
2025-11-02 21:18:15 +08:00
|
|
|
position += 1;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
// This line was forced into the output even if we
|
|
|
|
|
// were in flattened mode, so we need to tell the next
|
|
|
|
|
// group that no matter what, it needs to remeasure
|
|
|
|
|
// because the previous measurement didn't accurately
|
|
|
|
|
// capture the entire expression (this is necessary
|
|
|
|
|
// for nested groups)
|
|
|
|
|
shouldRemeasure = true;
|
|
|
|
|
}
|
|
|
|
|
// fallthrough
|
|
|
|
|
|
|
|
|
|
case MODE_BREAK:
|
|
|
|
|
if (lineSuffix.length > 0) {
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ indent, mode, doc }, ...lineSuffix.reverse());
|
2022-07-23 20:45:25 +08:00
|
|
|
lineSuffix.length = 0;
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (doc.literal) {
|
2025-11-02 21:18:15 +08:00
|
|
|
out.push(newLine);
|
|
|
|
|
position = 0;
|
|
|
|
|
if (indent.root) {
|
2025-11-03 23:35:48 +08:00
|
|
|
if (indent.root.value) {
|
|
|
|
|
out.push(indent.root.value);
|
|
|
|
|
}
|
2025-11-02 21:18:15 +08:00
|
|
|
position = indent.root.length;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-11-04 03:54:35 +08:00
|
|
|
trim();
|
2025-11-02 21:18:15 +08:00
|
|
|
out.push(newLine + indent.value);
|
|
|
|
|
position = indent.length;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LABEL:
|
2025-11-02 21:18:15 +08:00
|
|
|
commands.push({ indent, mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_BREAK_PARENT:
|
|
|
|
|
// No op
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
default:
|
|
|
|
|
throw new InvalidDocError(doc);
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
2020-11-18 05:58:46 -05:00
|
|
|
|
|
|
|
|
// Flush remaining line-suffix contents at the end of the document, in case
|
|
|
|
|
// there is no new line after the line-suffix.
|
2025-11-02 21:18:15 +08:00
|
|
|
if (commands.length === 0 && lineSuffix.length > 0) {
|
|
|
|
|
commands.push(...lineSuffix.reverse());
|
2022-07-23 20:45:25 +08:00
|
|
|
lineSuffix.length = 0;
|
2020-11-18 05:58:46 -05:00
|
|
|
}
|
2017-01-05 12:21:57 -05:00
|
|
|
}
|
2017-06-01 18:52:29 -04:00
|
|
|
|
2025-11-04 03:54:35 +08:00
|
|
|
const formatted = out.join("");
|
|
|
|
|
if (cursorPositions.length !== 2) {
|
|
|
|
|
// If the doc contained ONE cursor command,
|
|
|
|
|
// instead of the expected zero or two. If the doc being printed was
|
|
|
|
|
// returned by printAstToDoc, then the only ways this can have happened
|
|
|
|
|
// are if:
|
|
|
|
|
// 1. a plugin added a cursor command itself, or
|
|
|
|
|
// 2. one (but not both) of options.nodeAfterCursor and
|
|
|
|
|
// options.nodeAfterCursor pointed to a node within a subtree of the
|
|
|
|
|
// AST that the printer plugin used in printAstToDoc simply omits from
|
|
|
|
|
// the doc, or that it prints without recursively calling mainPrint,
|
|
|
|
|
// with the consequence that the logic for adding a cursor command in
|
|
|
|
|
// callPluginPrintFunction was never called for that node.
|
|
|
|
|
// These are both weird scenarios that should be considered a bug if they
|
|
|
|
|
// ever occur with one of Prettier's built-in plugins. If a third-party
|
|
|
|
|
// plugin was used when printing the AST to a doc, the possibility of
|
|
|
|
|
// reaching this scenario MIGHT be reasonable to consider a bug in the
|
|
|
|
|
// plugin. However, we try to at least not crash if this ever happens;
|
|
|
|
|
// instead we simply give up on returning a cursorNodeStart or
|
|
|
|
|
// cursorNodeText.
|
|
|
|
|
//
|
|
|
|
|
// coreFormat has logic specifically to handle this scenario - where it
|
|
|
|
|
// is supposed to preserve the cursor position but the printer gives it
|
|
|
|
|
// no information about where the nodes around the cursor ended up -
|
|
|
|
|
// although that logic is unavoidably slower (and has more potential to
|
|
|
|
|
// return a perverse result) than the happy path where we help out
|
|
|
|
|
// coreFormat by returning a cursorNodeStart and cursorNodeText here.
|
|
|
|
|
return { formatted };
|
2017-06-01 18:52:29 -04:00
|
|
|
}
|
|
|
|
|
|
2025-11-04 03:54:35 +08:00
|
|
|
const cursorNodeStart = cursorPositions[0] + 1;
|
|
|
|
|
return {
|
|
|
|
|
formatted,
|
|
|
|
|
cursorNodeStart,
|
|
|
|
|
cursorNodeText: formatted.slice(
|
|
|
|
|
cursorNodeStart,
|
|
|
|
|
cursorPositions.at(-1) + 1,
|
|
|
|
|
),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function trim() {
|
|
|
|
|
const { text, count } = trimIndentation(out);
|
|
|
|
|
out = [text];
|
|
|
|
|
|
|
|
|
|
if (count === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
position -= count;
|
|
|
|
|
|
|
|
|
|
if (cursorPositions.length === 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let index = 0; index < cursorPositions.length; index++) {
|
|
|
|
|
cursorPositions[index] = Math.min(
|
|
|
|
|
cursorPositions[index],
|
|
|
|
|
text.length - 1,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-28 11:12:37 +08:00
|
|
|
export { printDocToString };
|