Skip to content

Latest commit

 

History

History
340 lines (230 loc) · 10.3 KB

commands.md

File metadata and controls

340 lines (230 loc) · 10.3 KB

The core of the algorithm is implemented in src/document/{printer,builders,utils}.js. The printer uses the basic formatting abstractions provided to construct a format when printing a node.

Prettier's intermediate representation: Doc

A doc can be a string, an array of docs, or a command.

type Doc = string | Doc[] | DocCommand;
  • strings are printed directly as is (however for the algorithm to work properly they shouldn't contain line break characters)
  • arrays are used to concatenate a list of docs to be printed sequentially into a single doc
  • DocCommand is any of the following:

group

type GroupOptions = {
  shouldBreak?: boolean;
  id?: symbol;
};
declare function group(doc: Doc, options?: GroupOptions): Doc;

Mark a group of items which the printer should try to fit on one line. This is the basic command to tell the printer when to break. Groups are usually nested, and the printer will try to fit everything on one line, but if it doesn't fit it will break the outermost group first and try again. It will continue breaking groups until everything fits (or there are no more groups to break).

A group is forced to break if it's created with the shouldBreak option set to true or if it includes breakParent. A hard and literal line breaks automatically include this so they always break parent groups. Breaks are propagated to all parent groups, so if a deeply nested expression has a hard break, everything will break. This only matters for "hard" breaks, i.e. newlines that are printed no matter what and can be statically analyzed.

For example, an array will try to fit on one line:

[1, "foo", { bar: 2 }];

However, if any of the items inside the array have a hard break, the array will always break as well:

[
  1,
  function () {
    return 2;
  },
  3,
];

Functions always break after the opening curly brace no matter what, so the array breaks as well for consistent formatting. See the implementation of ArrayExpression for an example.

The id option can be used in ifBreak checks.

conditionalGroup

This should be used as last resort as it triggers an exponential complexity when nested.

declare function conditionalGroup(
  alternatives: Doc[],
  options?: GroupOptions,
): Doc;

This will try to print the first alternative, if it fit use it, otherwise go to the next one and so on. The alternatives is an array of documents going from the least expanded (most flattened) representation first to the most expanded.

conditionalGroup([a, b, c]);

fill

declare function fill(docs: Doc[]): Doc;

This is an alternative type of group which behaves like text layout: it's going to add a break whenever the next element doesn't fit in the line anymore. The difference with group is that it's not going to break all the separators, just the ones that are at the end of lines.

fill(["I", line, "love", line, "Prettier"]);

Expects the docs argument to be an array of alternating content and line breaks. In other words, elements with odd indices must be line breaks (e.g., softline).

ifBreak

declare function ifBreak(
  breakContents: Doc,
  flatContents?: Doc,
  options?: { groupId?: symbol },
): Doc;

Print something if the current group or the current element of fill breaks and something else if it doesn't.

ifBreak(";", " ");

groupId can be used to check another already printed group instead of the current group.

If a hardline or breakParent is present within the possible contents, the parent groups will be broken regardless of said content being printed, which might not be desirable. This behaviour is a design limitation. Usually the desired result can be achieved in a different way.

In the rare case that hardline is definitely needed, consider using hardlineWithoutBreakParent instead to avoid an unwanted group break propagation.

breakParent

declare const breakParent: Doc;

Include this anywhere to force all parent groups to break. See group for more info. Example:

group([" ", expr, " ", breakParent]);

join

declare function join(sep: Doc, docs: Doc[]): Doc;

Join an array of docs with a separator.

line

declare const line: Doc;

Specify a line break. If an expression fits on one line, the line break will be replaced with a space. Line breaks always indent the next line with the current level of indentation.

softline

declare const softline: Doc;

Specify a line break. The difference from line is that if the expression fits on one line, it will be replaced with nothing.

hardline

declare const hardline: Doc;

Specify a line break that is always included in the output, no matter if the expression fits on one line or not.

literalline

declare const literalline: Doc;

Specify a line break that is always included in the output and doesn't indent the next line. Also, unlike hardline, this kind of line break preserves trailing whitespace on the line it ends. This is used for template literals.

lineSuffix

declare function lineSuffix(suffix: Doc): Doc;

This is used to implement trailing comments. It's not practical to constantly check where the line ends to avoid accidentally printing some code at the end of a comment. lineSuffix buffers docs passed to it and flushes them before any new line.

["a", lineSuffix(" // comment"), ";", hardline];

will output

a; // comment

lineSuffixBoundary

declare const lineSuffixBoundary: Doc;

In cases where you embed code inside of templates, comments shouldn't be able to leave the code part. lineSuffixBoundary is an explicit marker you can use to flush the lineSuffix buffer in addition to line breaks.

["{", lineSuffix(" // comment"), lineSuffixBoundary, "}", hardline];

will output

{ // comment
}

and not

{} // comment

indent

declare function indent(doc: Doc): Doc;

Increase the level of indentation.

dedent

declare function dedent(doc: Doc): Doc;

Decrease the level of indentation. (Each align is considered one level of indentation.)

align

declare function align(widthOrString: number | string, doc: Doc): Doc;

Increase the indentation by a fixed number of spaces or a string. A variant of indent.

When useTabs is enabled, trailing alignments in indentation are still spaces, but middle ones are transformed one tab per align. In a whitespace-sensitive context (e.g., Markdown), you should pass spaces to align as strings to prevent their replacement with tabs.

For example:

  • useTabs
    • tabWidth: 2
      • <indent><align 2><indent><align 2> -> <tab><tab><tab><2 space>
      • <indent><align 4><indent><align 2> -> <tab><tab><tab><2 space>
    • tabWidth: 4
      • <indent><align 2><indent><align 2> -> <tab><tab><tab><2 space>
      • <indent><align 4><indent><align 2> -> <tab><tab><tab><2 space>

markAsRoot

declare function markAsRoot(doc: Doc): Doc;

Mark the current indentation as root for dedentToRoot and literallines.

dedentToRoot

declare function dedentToRoot(doc: Doc): Doc;

Decrease the current indentation to the root marked by markAsRoot.

trim

declare const trim: Doc;

Trim all the indentation on the current line. This can be used for preprocessor directives. Should be placed after a line break.

indentIfBreak

Added in v2.3.0

declare function indentIfBreak(
  doc: Doc,
  opts: { groupId: symbol; negate?: boolean },
): Doc;

An optimized version of ifBreak(indent(doc), doc, { groupId }).

With negate: true, corresponds to ifBreak(doc, indent(doc), { groupId })

It doesn't make sense to apply indentIfBreak to the current group because "indent if the current group is broken" is the normal behavior of indent. That's why groupId is required.

label

Added in v2.3.0

declare function label(label: any, doc: Doc): Doc;

Mark a doc with an arbitrary truthy value. This doesn't affect how the doc is printed, but can be useful for heuristics based on doc introspection.

E.g., to decide how to print an assignment expression, we might want to know whether its right-hand side has been printed as a method call chain, not as a plain function call. If the method chain printing code uses label to mark its result, checking that condition can be as easy as rightHandSideDoc.label === 'method-chain'.

If the label argument is falsy, doc is returned as is, without wrapping.

hardlineWithoutBreakParent and literallineWithoutBreakParent

Added in v2.3.0

declare const hardlineWithoutBreakParent: Doc;
declare const literallineWithoutBreakParent: Doc;

These are used very rarely, for advanced formatting tricks. Unlike their "normal" counterparts, they don't include an implicit breakParent.

Examples:

  • hardlineWithoutBreakParent is used for printing tables in Prettier's Markdown printer. With proseWrap set to never, the columns are aligned only if none of the rows exceeds printWidth.
  • literallineWithoutBreakParent is used in the Ruby plugin for printing heredoc syntax.

cursor

declare const cursor: Doc;

This is a placeholder value where the cursor is in the original input in order to find where it would be printed.

Example

For an example, here's the implementation of the ArrayExpression node type:

group(
  [
    "[",
    indent(
      [
        line,
        join(
          [",", line],
          path.map(print, "elements")
        )
      ]
    ),
    line,
    "]"
  ]
);

This is a group with opening and closing brackets, and possibly indented contents. Because it's a group it will always be broken up if any of the sub-expressions are broken.