diff --git a/docs/plugins.md b/docs/plugins.md index 66317e660..b60c236f6 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -136,25 +136,44 @@ function preprocess(text: string, options: object): string; Printers convert ASTs into a Prettier intermediate representation, also known as a Doc. -The key must match the `astFormat` that the parser produces. The value contains an object with a `print` function and (optionally) an `embed` function. +The key must match the `astFormat` that the parser produces. The value contains an object with a `print` function. All other properties (`embed`, `preprocess`, etc.) are optional. ```js export const printers = { "dance-ast": { print, embed, + preprocess, insertPragma, + canAttachComment, + isBlockComment, + printComment, + handleComments: { + ownLine, + endOfLine, + remaining, + }, }, }; ``` -Printing is a recursive process of converting an AST node (represented by a path to that node) into a doc. The doc is constructed using the [builder commands](https://github.com/prettier/prettier/blob/master/commands.md): +#### The printing process + +Prettier uses an intermediate representation, called a Doc, which Prettier then turns into a string (based on options like `printWidth`). A _printer_'s job is to take the AST generated by `parsers[].parse` and return a Doc. A Doc is constructed using [builder commands](https://github.com/prettier/prettier/blob/master/commands.md): ```js const { concat, join, line, ifBreak, group } = require("prettier").doc.builders; ``` -The signature of the `print` function is: +The printing process works as follows: + +1. `preprocess(ast: AST, options: object): AST`, if available, is called. It is passed the AST from the _parser_. The AST returned by `preprocess` will be used by Prettier. If `preprocess` is not defined, the AST returned from the _parser_ will be used. +2. Comments are attached to the AST (see _Handling comments in a printer_ for details). +3. A Doc is recursively constructed from the AST. i) `embed(path: FastPath, print, textToDoc, options: object): Doc | null` is called on each AST node. If `embed` returns a Doc, that Doc is used. ii) If `embed` is undefined or returns a falsy value, `print(path: FastPath, options: object, print): Doc` is called on each AST node. + +#### `print` + +Most of the work of a plugin's printer will take place in its `print` function, whose signature is: ```ts function print( @@ -166,9 +185,26 @@ function print( ): Doc; ``` -Check out [prettier-python’s printer](https://github.com/prettier/prettier-python/blob/034ba8a9551f3fa22cead41b323be0b28d06d13b/src/printer.js#L174) as an example. +The `print` function is passed a `path` object, which can be used to access nodes in the AST via `path.getValue()`. It is also passed a persistent `options` object (which contains global options and which a plugin may mutate), and a `print` function used for making recursive calls. A basic `print` function might be as follows: -Embedding refers to printing one language inside another. Examples of this are CSS-in-JS and Markdown code blocks. Plugins can switch to alternate languages using the `embed` function. Its signature is: +```js +const { builders } = require("prettier").doc; + +function print(path, options, print) { + const node = path.getValue(); + + if (Array.isArray(node)) { + return builders.concat(path.map(print)); + } + return node.value; +} +``` + +Check out [prettier-python's printer](https://github.com/prettier/prettier-python/blob/034ba8a9551f3fa22cead41b323be0b28d06d13b/src/printer.js#L174) for some examples of what is possible. + +#### (optional) `embed` + +The `embed` function is called when the plugin needs to print one language inside another. Examples of this are printing CSS-in-JS or fenced code blocks in Markdown. Its signature is: ```ts function embed( @@ -184,7 +220,29 @@ function embed( ): Doc | null; ``` -If an `embed` function is included, it will be called _first_ on every node. If the `embed` function returns a falsy value, the current plugin’s `print` function is called. Otherwise, the `Doc` returned by the `embed` function is used. The `embed` function is passed `textToDoc` which can be called with a suitable `options` argument to obtain the `Doc` produced by a different plugin (which can then be directly returned or used inside a larger `Doc` construction). For example, to print embedded JavaScript, a plugin might return `textToDoc(javaScriptText, { parser: "babel" })` from the `embed` function. +The `embed` function acts like the `print` function, except that it is passed an additional `textToDoc` function, which can be used to render a doc using a different plugin. The `embed` function returns a Doc or a falsy value. If a falsy value is returned, the `print` function is called with the current `path`. If a Doc is returned, that Doc is used in printing and the `print` function is not called. + +For example, a plugin that had nodes with embedded JavaScript might have the following `embed` function: + +```js +function embed(path, print, textToDoc, options) { + const node = path.getValue(); + if (node.type === "javascript") { + return textToDoc(node.javaScriptText, { ...options, parser: "babel" }); + } + return false; +} +``` + +#### (optional) `preprocess` + +The preprocess function can process the AST from parser before passing into `print` function. + +```ts +function preprocess(ast: AST, options: object): AST; +``` + +#### (optional) `insertPragma` A plugin can implement how a pragma comment is inserted in the resulting code when the `--insert-pragma` option is used, in the `insertPragma` function. Its signature is: @@ -192,12 +250,99 @@ A plugin can implement how a pragma comment is inserted in the resulting code wh function insertPragma(text: string): string; ``` -_(Optional)_ The preprocess function can process the ast from parser before passing into `print` function. +#### Handling comments in a printer + +Comments are often not part of a language's AST and present a challenge for pretty printers. A Prettier plugin can either print comments itself in its `print` function or rely on Prettier's comment algorithm. + +By default, if the AST has a top-level `comments` property, Prettier assumes that `comments` stores an array of comment nodes. Prettier will then use the provided `parsers[].locStart`/`locEnd` functions to search for the AST node that each comment "belongs" to. Comments are then attached to these nodes **mutating the AST in the process**, and the `comments` property is deleted from the AST root. The `*Comment` functions are used to adjust Prettier's algorithm. Once the comments are attached to the AST, Prettier will automatically call the `printComment(path, options): Doc` function and insert the returned doc into the (hopefully) correct place. + +#### (optional) `printComment` + +Called whenever a comment node needs to be printed. It has the signature: ```ts -function preprocess(ast: AST, options: object): AST; +function printComment( + // Path to the current comment node + commentPath: FastPath, + // Current options + options: object +): Doc; ``` +#### (optional) `canAttachComment` + +```ts +function canAttachComment(node: AST): boolean; +``` + +This function is used for deciding whether a comment can be attached to a particular AST node. By default, _all_ AST properties are traversed searching for nodes that comments can be attached to. This function is used to prevent comments from being attached to a particular node. A typical implementation looks like + +```js +function canAttachComment(node) { + return node.type && node.type !== "comment"; +} +``` + +#### (optional) `isBlockComment` + +```ts +function isBlockComment(node: AST): boolean; +``` + +Returns whether or not the AST node is a block comment. + +#### (optional) `handleComments` + +The `handleComments` object contains three optional functions, each with signature + +```ts +function( + // The AST node corresponding to the comment + comment: AST, + // The full source code text + text: string, + // The global options object + options: object, + // The AST + ast: AST, + // Whether this comment is the last comment + isLastComment: boolean +): boolean +``` + +These functions are used to override Prettier's default comment attachment algorithm. `ownLine`/`endOfLine`/`remaining` is expected to either manually attach a comment to a node and return `true`, or return `false` and let Prettier attach the comment. + +Based on the text surrounding a comment node, Prettier dispatches: + +- `ownLine` if a comment has only whitespace preceding it and a newline afterwards, +- `endOfLine` if a comment has a newline afterwards but some non-whitespace preceding it, +- `remaining` in all other cases. + +At the time of dispatching, Prettier will have annotated each AST comment node (i.e., created new properties) with at least one of `enclosingNode`, `precedingNode`, or `followingNode`. These can be used to aid a plugin's decision process (of course the entire AST and original text is also passed in for making more complicated decisions). + +#### Manually attaching a comment + +The `util.addTrailingComment`/`addLeadingComment`/`addDanglingComment` functions can be used to manually attach a comment to an AST node. An example `ownLine` function that ensures a comment does not follow a "punctuation" node (made up for demonstration purposes) might look like: + +```js +const { util } = require("prettier"); + +function ownLine(comment, text, options, ast, isLastComment) { + const { precedingNode } = comment; + if (precedingNode && precedingNode.type === "punctuation") { + util.addTrailingComment(precedingNode, comment); + return true; + } + return false; +} +``` + +Nodes with comments are expected to have a `comments` property containing an array of comments. Each comment is expected to have the following properties: `leading`, `trailing`, `printed`. + + + +The example above uses `util.addTrailingComment`, which automatically sets `comment.leading`/`trailing`/`printed` to appropriate values and adds the comment to the AST node's `comments` array. + ### `options` `options` is an object containing the custom options your plugin supports.