Updated plugin documentation (#8407)

* Updated plugin documentation

* Spellcheck

* Wording change: attributes -> properties

* Corrected explanation

* Wording fixes

* Proofreading/nitpicking

* Reformat

* Reformat with --prose-wrap always

* Revert "Reformat with --prose-wrap always"

This reverts commit 2518a08a32818caa640303aede1c54a7ee1e5ab9.

* Reformat with --prose-wrap always

* Reformat with --prose-wrap never

Co-authored-by: Georgii Dolzhykov <thorn.mailbox@gmail.com>
This commit is contained in:
Jason SiefkenandGeorgii Dolzhykov authored and GitHub committed 2020-08-30 13:28:40 +03:00
1 parent cfd6074e44
commit c891c38fd9
1 file changed
+153 -8
+153 -8
View File
@@ -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[<parser name>].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[<plugin>].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`.
<!-- TODO: add a note that this might change in the future -->
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.