Release 3.0.0
This commit is contained in:
1 parent
44979b0a37
commit
186e0966b0
15 files changed
+444
-227
No files matched your search
@@ -8,26 +8,21 @@ Plugins are ways of adding new languages or formatting rules to Prettier. Pretti
|
||||
|
||||
## Using Plugins
|
||||
|
||||
Plugins are automatically loaded if you have them installed in the same `node_modules` directory where `prettier` is located. Plugin package names must start with `@prettier/plugin-` or `prettier-plugin-` or `@<scope>/prettier-plugin-` to be registered.
|
||||
You can load plugins with:
|
||||
|
||||
> `<scope>` should be replaced by a name, read more about [NPM scope](https://docs.npmjs.com/misc/scope.html).
|
||||
|
||||
When plugins cannot be found automatically, you can load them with:
|
||||
|
||||
- The [CLI](cli.md), via `--plugin-search-dir` and `--plugin`:
|
||||
- The [CLI](cli.md), via `--plugin`:
|
||||
|
||||
```bash
|
||||
prettier --write main.foo --plugin-search-dir=./dir-with-plugins --plugin=prettier-plugin-foo
|
||||
prettier --write main.foo --plugin=prettier-plugin-foo
|
||||
```
|
||||
|
||||
> Tip: You can set `--plugin-search-dir` or `--plugin` options multiple times.
|
||||
> Tip: You can set `--plugin` options multiple times.
|
||||
|
||||
- The [API](api.md), via the `pluginSearchDirs` and `plugins` options:
|
||||
- The [API](api.md), via the `plugins` options:
|
||||
|
||||
```js
|
||||
prettier.format("code", {
|
||||
await prettier.format("code", {
|
||||
parser: "foo",
|
||||
pluginSearchDirs: ["./dir-with-plugins"],
|
||||
plugins: ["prettier-plugin-foo"],
|
||||
});
|
||||
```
|
||||
@@ -36,18 +31,11 @@ When plugins cannot be found automatically, you can load them with:
|
||||
|
||||
```json
|
||||
{
|
||||
"pluginSearchDirs": ["./dir-with-plugins"],
|
||||
"plugins": ["prettier-plugin-foo"]
|
||||
}
|
||||
```
|
||||
|
||||
`pluginSearchDirs` and `plugins` are independent and one does not require the other.
|
||||
|
||||
The paths that are provided to `pluginSearchDirs` will be searched for `@prettier/plugin-*`, `prettier-plugin-*`, and `@*/prettier-plugin-*`. For instance, these can be your project directory, a `node_modules` directory, the location of global npm modules, or any arbitrary directory that contains plugins.
|
||||
|
||||
Strings provided to `plugins` are ultimately passed to `require()`, so you can provide a module/package name, a path, or anything else `require()` takes. (`pluginSearchDirs` works the same way. That is, valid plugin paths that it finds are passed to `require()`.)
|
||||
|
||||
To turn off plugin autoloading, use `--no-plugin-search` when using Prettier CLI or add `{ pluginSearchDirs: false }` to options in `prettier.format()` or to the config file.
|
||||
Strings provided to `plugins` are ultimately passed to [`import()` expression](https://nodejs.org/api/esm.html#import-expressions), so you can provide a module/package name, a path, or anything else `import()` takes.
|
||||
|
||||
## Official Plugins
|
||||
|
||||
@@ -81,7 +69,7 @@ To turn off plugin autoloading, use `--no-plugin-search` when using Prettier CLI
|
||||
|
||||
## Developing Plugins
|
||||
|
||||
Prettier plugins are regular JavaScript modules with five exports:
|
||||
Prettier plugins are regular JavaScript modules with the following five exports or default export with the following properties:
|
||||
|
||||
- `languages`
|
||||
- `parsers`
|
||||
@@ -130,7 +118,7 @@ export const parsers = {
|
||||
The signature of the `parse` function is:
|
||||
|
||||
```ts
|
||||
function parse(text: string, parsers: object, options: object): AST;
|
||||
function parse(text: string, options: object): Promise<AST> | AST;
|
||||
```
|
||||
|
||||
The location extraction functions (`locStart` and `locEnd`) return the starting and ending locations of a given AST node:
|
||||
@@ -163,6 +151,7 @@ export const printers = {
|
||||
print,
|
||||
embed,
|
||||
preprocess,
|
||||
getVisitorKeys,
|
||||
insertPragma,
|
||||
canAttachComment,
|
||||
isBlockComment,
|
||||
@@ -182,14 +171,19 @@ export const printers = {
|
||||
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/main/commands.md):
|
||||
|
||||
```js
|
||||
const { join, line, ifBreak, group } = require("prettier").doc.builders;
|
||||
import { doc } from "prettier";
|
||||
|
||||
const { join, line, ifBreak, group } = doc.builders;
|
||||
```
|
||||
|
||||
The printing process works as follows:
|
||||
The printing process consists of the following steps:
|
||||
|
||||
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: AstPath, 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: AstPath, options: object, print): Doc` is called on each AST node.
|
||||
1. **AST preprocessing** (optional). See [`preprocess`](#optional-preprocess).
|
||||
2. **Comment attachment** (optional). See [Handling comments in a printer](#handling-comments-in-a-printer).
|
||||
3. **Processing embedded languages** (optional). The [`embed`](#optional-embed) method, if defined, is called for each node, depth-first. While, for performance reasons, the recursion itself is synchronous, `embed` may return asynchronous functions that can call other parsers and printers to compose docs for embedded syntaxes like CSS-in-JS. These returned functions are queued up and sequentially executed before the next step.
|
||||
4. **Recursive printing**. A doc is recursively constructed from the AST. Starting from the root node:
|
||||
- If, from the step 3, there is an embedded language doc associated with the current node, this doc is used.
|
||||
- Otherwise, the `print(path, options, print): Doc` method is called. It composes a doc for the current node, often by printing child nodes using the `print` callback.
|
||||
|
||||
#### `print`
|
||||
|
||||
@@ -214,9 +208,9 @@ The `print` function is passed the following parameters:
|
||||
Here’s a simplified example to give an idea of what a typical implementation of `print` looks like:
|
||||
|
||||
```js
|
||||
const {
|
||||
builders: { group, indent, join, line, softline },
|
||||
} = require("prettier").doc;
|
||||
import { doc } from "prettier";
|
||||
|
||||
const { group, indent, join, line, softline } = doc.builders;
|
||||
|
||||
function print(path, options, print) {
|
||||
const node = path.getValue();
|
||||
@@ -250,42 +244,112 @@ Check out [prettier-python's printer](https://github.com/prettier/prettier-pytho
|
||||
|
||||
#### (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:
|
||||
A printer can have the `embed` method to print one language inside another. Examples of this are printing CSS-in-JS or fenced code blocks in Markdown. The signature is:
|
||||
|
||||
```ts
|
||||
function embed(
|
||||
// Path to the current AST node
|
||||
path: AstPath,
|
||||
// Print a node with the current printer
|
||||
print: (selector?: string | number | Array<string | number> | AstPath) => Doc,
|
||||
// Parse and print some text using a different parser.
|
||||
// You should set `options.parser` to specify which parser to use.
|
||||
textToDoc: (text: string, options: object) => Doc,
|
||||
// Current options
|
||||
options: object
|
||||
): Doc | null;
|
||||
options: Options
|
||||
):
|
||||
| ((
|
||||
// Parses and prints the passed text using a different parser.
|
||||
// You should set `options.parser` to specify which parser to use.
|
||||
textToDoc: (text: string, options: Options) => Promise<Doc>,
|
||||
// Prints the current node or its descendant node with the current printer
|
||||
print: (
|
||||
selector?: string | number | Array<string | number> | AstPath
|
||||
) => Doc,
|
||||
// The following two arguments are passed for convenience.
|
||||
// They're the same `path` and `options` that are passed to `embed`.
|
||||
path: AstPath,
|
||||
options: Options
|
||||
) => Promise<Doc | undefined> | Doc | undefined)
|
||||
| Doc
|
||||
| undefined;
|
||||
```
|
||||
|
||||
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.
|
||||
The `embed` method is similar to the `print` method in that it maps AST nodes to docs, but unlike `print`, it has power to do async work by returning an async function. That function's first parameter, the `textToDoc` async function, can be used to render a doc using a different plugin.
|
||||
|
||||
For example, a plugin that had nodes with embedded JavaScript might have the following `embed` function:
|
||||
If a function returned from `embed` returns a doc or a promise that resolves to a doc, that doc will be used in printing, and the `print` method won’t be called for this node. It's also possible and, in rare situations, might be convenient to return a doc synchronously directly from `embed`, however `textToDoc` and the `print` callback aren’t available at that case. Return a function to get them.
|
||||
|
||||
If `embed` returns `undefined`, or if a function it returned returns `undefined` or a promise that resolves to `undefined`, the node will be printed normally with the `print` method. Same will happen if a returned function throws an error or returns a promise that rejects (e.g., if a parsing error has happened). Set the `PRETTIER_DEBUG` environment variable to a non-empty value if you want Prettier to rethrow these errors.
|
||||
|
||||
For example, a plugin that has nodes with embedded JavaScript might have the following `embed` method:
|
||||
|
||||
```js
|
||||
function embed(path, print, textToDoc, options) {
|
||||
function embed(path, options) {
|
||||
const node = path.getValue();
|
||||
if (node.type === "javascript") {
|
||||
return textToDoc(node.javaScriptText, { ...options, parser: "babel" });
|
||||
return async (textToDoc) => {
|
||||
return [
|
||||
"<script>",
|
||||
hardline,
|
||||
await textToDoc(node.javaScriptCode, { parser: "babel" }),
|
||||
hardline,
|
||||
"</script>",
|
||||
];
|
||||
};
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
If the [`--embedded-language-formatting`](options.md#embedded-language-formatting) option is set to `off`, the embedding step is entirely skipped, `embed` isn’t called, and all nodes are printed with the `print` method.
|
||||
|
||||
#### (optional) `preprocess`
|
||||
|
||||
The preprocess function can process the AST from parser before passing into `print` function.
|
||||
The `preprocess` method can process the AST from the parser before passing it into the `print` method.
|
||||
|
||||
```ts
|
||||
function preprocess(ast: AST, options: object): AST;
|
||||
function preprocess(ast: AST, options: Options): AST | Promise<AST>;
|
||||
```
|
||||
|
||||
#### (optional) `getVisitorKeys`
|
||||
|
||||
This property might come in handy if the plugin uses comment attachment or embedded languages. These features traverse the AST iterating through all the own enumerable properties of each node starting from the root. If the AST has [cycles](<https://en.wikipedia.org/wiki/Cycle_(graph_theory)>), such a traverse ends up in an infinite loop. Also, nodes might contain non-node objects (e.g., location data), iterating through which is a waste of resources. To solve these issues, the printer can define a function to return property names that should be traversed.
|
||||
|
||||
Its signature is:
|
||||
|
||||
```ts
|
||||
function getVisitorKeys(node, nonTraversableKeys: Set<string>): string[];
|
||||
```
|
||||
|
||||
The default `getVisitorKeys`:
|
||||
|
||||
```js
|
||||
function getVisitorKeys(node, nonTraversableKeys) {
|
||||
return Object.keys(node).filter((key) => !nonTraversableKeys.has(key));
|
||||
}
|
||||
```
|
||||
|
||||
The second argument `nonTraversableKeys` is a set of common keys and keys that prettier used internal.
|
||||
|
||||
If you have full list of visitor keys
|
||||
|
||||
```js
|
||||
const visitorKeys = {
|
||||
Program: ["body"],
|
||||
Identifier: [],
|
||||
// ...
|
||||
};
|
||||
|
||||
function getVisitorKeys(node /* , nonTraversableKeys*/) {
|
||||
// Return `[]` for unknown node to prevent Prettier fallback to use `Object.keys()`
|
||||
return visitorKeys[node.type] ?? [];
|
||||
}
|
||||
```
|
||||
|
||||
If you only need exclude a small set of keys
|
||||
|
||||
```js
|
||||
const ignoredKeys = new Set(["prev", "next", "range"]);
|
||||
|
||||
function getVisitorKeys(node, nonTraversableKeys) {
|
||||
return Object.keys(node).filter(
|
||||
(key) => !nonTraversableKeys.has(key) && !ignoredKeys.has(key)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### (optional) `insertPragma`
|
||||
@@ -357,18 +421,18 @@ Returns whether or not the AST node is a block comment.
|
||||
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
|
||||
(
|
||||
// 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.
|
||||
@@ -386,7 +450,7 @@ At the time of dispatching, Prettier will have annotated each AST comment node (
|
||||
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");
|
||||
import { util } from "prettier";
|
||||
|
||||
function ownLine(comment, text, options, ast, isLastComment) {
|
||||
const { precedingNode } = comment;
|
||||
@@ -413,54 +477,137 @@ The `--debug-print-comments` CLI flag can help with debugging comment attachment
|
||||
Example:
|
||||
|
||||
```js
|
||||
options: {
|
||||
openingBraceNewLine: {
|
||||
type: "boolean",
|
||||
category: "Global",
|
||||
default: true,
|
||||
description: "Move open brace for code blocks onto new line."
|
||||
}
|
||||
}
|
||||
export default {
|
||||
// ... plugin implementation
|
||||
options: {
|
||||
openingBraceNewLine: {
|
||||
type: "boolean",
|
||||
category: "Global",
|
||||
default: true,
|
||||
description: "Move open brace for code blocks onto new line.",
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### `defaultOptions`
|
||||
|
||||
If your plugin requires different default values for some of Prettier’s core options, you can specify them in `defaultOptions`:
|
||||
|
||||
```
|
||||
defaultOptions: {
|
||||
tabWidth: 4
|
||||
}
|
||||
```js
|
||||
export default {
|
||||
// ... plugin implementation
|
||||
defaultOptions: {
|
||||
tabWidth: 4,
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Utility functions
|
||||
|
||||
A `util` module from Prettier core is considered a private API and is not meant to be consumed by plugins. Instead, the `util-shared` module provides the following limited set of utility functions for plugins:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
type Quote = '"' | "'";
|
||||
type SkipOptions = { backwards?: boolean };
|
||||
function getMaxContinuousCount(str: string, target: string): number;
|
||||
|
||||
function getMaxContinuousCount(text: string, searchString: string): number;
|
||||
|
||||
function getStringWidth(text: string): number;
|
||||
function getAlignmentSize(value: string, tabWidth: number, startIndex?: number): number;
|
||||
|
||||
function getAlignmentSize(
|
||||
text: string,
|
||||
tabWidth: number,
|
||||
startIndex?: number
|
||||
): number;
|
||||
|
||||
function getIndentSize(value: string, tabWidth: number): number;
|
||||
function skip(chars: string | RegExp): (text: string, index: number | false, opts?: SkipOptions) => number | false;
|
||||
function skipWhitespace(text: string, index: number | false, opts?: SkipOptions): number | false;
|
||||
function skipSpaces(text: string, index: number | false, opts?: SkipOptions): number | false;
|
||||
function skipToLineEnd(text: string, index: number | false, opts?: SkipOptions): number | false;
|
||||
function skipEverythingButNewLine(text: string, index: number | false, opts?: SkipOptions): number | false;
|
||||
function skipInlineComment(text: string, index: number | false): number | false;
|
||||
function skipTrailingComment(text: string, index: number | false): number | false;
|
||||
function skipNewline(text: string, index: number | false, opts?: SkipOptions): number | false;
|
||||
function hasNewline(text: string, index: number, opts?: SkipOptions): boolean;
|
||||
function hasNewlineInRange(text: string, start: number, end: number): boolean;
|
||||
function hasSpaces(text: string, index: number, opts?: SkipOptions): boolean;
|
||||
function makeString(rawContent: string, enclosingQuote: Quote, unescapeUnnecessaryEscapes?: boolean): string;
|
||||
function getNextNonSpaceNonCommentCharacterIndex<N>(text: string, node: N, locEnd: (node: N) => number): number | false;
|
||||
function isNextLineEmptyAfterIndex(text: string, index: number): boolean;
|
||||
function isNextLineEmpty<N>(text: string, node: N, locEnd: (node: N) => number): boolean;
|
||||
function isPreviousLineEmpty<N>(text: string, node: N, locStart: (node: N) => number): boolean;
|
||||
|
||||
function skip(
|
||||
characters: string | RegExp
|
||||
): (
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions
|
||||
) => number | false;
|
||||
|
||||
function skipWhitespace(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions
|
||||
): number | false;
|
||||
|
||||
function skipSpaces(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions
|
||||
): number | false;
|
||||
|
||||
function skipToLineEnd(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions
|
||||
): number | false;
|
||||
|
||||
function skipEverythingButNewLine(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions
|
||||
): number | false;
|
||||
|
||||
function skipInlineComment(
|
||||
text: string,
|
||||
startIndex: number | false
|
||||
): number | false;
|
||||
|
||||
function skipTrailingComment(
|
||||
text: string,
|
||||
startIndex: number | false
|
||||
): number | false;
|
||||
|
||||
function skipNewline(
|
||||
text: string,
|
||||
startIndex: number | false,
|
||||
options?: SkipOptions
|
||||
): number | false;
|
||||
|
||||
function hasNewline(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
options?: SkipOptions
|
||||
): boolean;
|
||||
|
||||
function hasNewlineInRange(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
startIndex: number
|
||||
): boolean;
|
||||
|
||||
function hasSpaces(
|
||||
text: string,
|
||||
startIndex: number,
|
||||
options?: SkipOptions
|
||||
): boolean;
|
||||
|
||||
function makeString(
|
||||
rawText: string,
|
||||
enclosingQuote: Quote,
|
||||
unescapeUnnecessaryEscapes?: boolean
|
||||
): string;
|
||||
|
||||
function getNextNonSpaceNonCommentCharacter(
|
||||
text: string,
|
||||
startIndex: number
|
||||
): string;
|
||||
|
||||
function getNextNonSpaceNonCommentCharacterIndex(
|
||||
text: string,
|
||||
startIndex: number
|
||||
): number | false;
|
||||
|
||||
function isNextLineEmpty(text: string, startIndex: number): boolean;
|
||||
|
||||
function isPreviousLineEmpty(text: string, startIndex: number): boolean;
|
||||
```
|
||||
|
||||
### Tutorials
|
||||
@@ -472,9 +619,9 @@ function isPreviousLineEmpty<N>(text: string, node: N, locStart: (node: N) => nu
|
||||
Since plugins can be resolved using relative paths, when working on one you can do:
|
||||
|
||||
```js
|
||||
const prettier = require("prettier");
|
||||
import * as prettier from "prettier";
|
||||
const code = "(add 1 2)";
|
||||
prettier.format(code, {
|
||||
await prettier.format(code, {
|
||||
parser: "lisp",
|
||||
plugins: ["."],
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user