Implement custom parser API (#1783)
* feat(api): add custom parser API * test(api): add integration test for parser resolution * chore(api): fix build after resolving conflicts * docs(api): document custom parser API * docs(api): fix typo * chore(build): add parse5 to build and support yarn test --prodmaster
parent
486a89bfdc
commit
364c38de0d
63
README.md
63
README.md
|
@ -10,8 +10,9 @@
|
|||
- [Usage](#usage)
|
||||
* [CLI](#cli)
|
||||
+ [Pre-commit hook for changed files](#pre-commit-hook-for-changed-files)
|
||||
* [API](#api)
|
||||
* [Options](#options)
|
||||
* [API](#api)
|
||||
+ [Custom Parser API](#custom-parser-api)
|
||||
* [Excluding code from formatting](#excluding-code-from-formatting)
|
||||
- [Editor Integration](#editor-integration)
|
||||
* [Atom](#atom)
|
||||
|
@ -246,6 +247,28 @@ echo >&2 "node_modules/.bin/prettier --write "$diffs""
|
|||
exit 1
|
||||
```
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
Prettier ships with a handful of customizable format options, usable in both the CLI and API.
|
||||
|
||||
| Option | Default | CLI override | API override |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **Print Width** - Specify the length of line that the printer will wrap on.<br /><br /><strong>We strongly recommend against using more than 80 columns</strong>. Prettier works by craming as much content as possible until it reaches the limit, which happens to work well for 80 columns but makes lines that are very crowded. When a bigger column count is used in styleguides, it usually means that code is allowed to go beyond 80 columns, but not to make every single line go there, like prettier would do. | `80` | `--print-width <int>` | `printWidth: <int>`
|
||||
| **Tab Width** - Specify the number of spaces per indentation-level. | `2` | `--tab-width <int>` | `tabWidth: <int>` |
|
||||
| **Tabs** - Indent lines with tabs instead of spaces. | `false` | `--use-tabs` | `useTabs: <bool>` |
|
||||
| **Semicolons** - Print semicolons at the ends of statements.<br /><br />Valid options: <br /> - `true` - add a semicolon at the end of every statement <br /> - `false` - only add semicolons at the beginning of lines that may introduce ASI failures | `true` | `--no-semi` | `semi: <bool>` |
|
||||
| **Quotes** - Use single quotes instead of double quotes.<br /><br />Notes:<br /> - Quotes in JSX will always be double and ignore this setting. <br /> - If the number of quotes outweighs the other quote, the quote which is less used will be used to format the string - Example: `"I'm double quoted"` results in `"I'm double quoted"` and `"This \"example\" is single quoted"` results in `'This "example" is single quoted'`. | `false` | `--single-quote` | `singleQuote: <bool>` |
|
||||
| **Trailing Commas** - Print trailing commas wherever possible.<br /><br />Valid options: <br /> - `"none"` - no trailing commas <br /> - `"es5"` - trailing commas where valid in ES5 (objects, arrays, etc) <br /> - `"all"` - trailing commas wherever possible (function arguments). This requires node 8 or a [transform](https://babeljs.io/docs/plugins/syntax-trailing-function-commas/). | `"none"` | <code>--trailing-comma <none|es5|all></code> | <code>trailingComma: "<none|es5|all>"</code> |
|
||||
| **Bracket Spacing** - Print spaces between brackets in object literals.<br /><br />Valid options: <br /> - `true` - Example: `{ foo: bar }` <br /> - `false` - Example: `{foo: bar}` | `true` | `--no-bracket-spacing` | `bracketSpacing: <bool>` |
|
||||
| **JSX Brackets on Same Line** - Put the `>` of a multi-line JSX element at the end of the last line instead of being alone on the next line | `false` | `--jsx-bracket-same-line` | `jsxBracketSameLine: <bool>` |
|
||||
| **Cursor Offset** - Specify where the cursor is. This option only works with `prettier.formatWithCursor`, and cannot be used with `rangeStart` and `rangeEnd`. | `-1` | `--cursor-offset <int>` | `cursorOffset: <int>` |
|
||||
| **Range Start** - Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. This option cannot be used with `cursorOffset`. | `0` | `--range-start <int>` | `rangeStart: <int>` |
|
||||
| **Range End** - Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. This option cannot be used with `cursorOffset`. | `Infinity` | `--range-end <int>` | `rangeEnd: <int>` |
|
||||
| **Parser** - Specify which parser to use. Both the `babylon` and `flow` parsers support the same set of JavaScript features (including Flow). Prettier automatically infers the parser from the input file path, so you shouldn't have to change this setting. [Custom parsers](#custom-parser-api) are supported. | `babylon` | <code>--parser <flow|babylon|typescript|postcss></code><br /><code>--parser ./path/to/my-parser</code> | <code>parser: "<flow|babylon|typescript|postcss>"</code><br /><code>parser: require("./my-parser")</code> |
|
||||
| **Filepath** - Specify the input filepath this will be used to do parser inference.<br /><br /> Example: <br />`cat foo \| prettier --stdin-filepath foo.css`<br /> will default to use `postcss` parser | | `--stdin-filepath` | `filepath: <string>` |
|
||||
|
||||
|
||||
### API
|
||||
|
||||
The API has three functions, exported as `format`, `check`, and `formatWithCursor`. `format` usage is as follows:
|
||||
|
@ -270,25 +293,29 @@ prettier.formatWithCursor(" 1", { cursorOffset: 2 });
|
|||
// -> { formatted: '1;\n', cursorOffset: 1 }
|
||||
```
|
||||
|
||||
### Options
|
||||
#### Custom Parser API
|
||||
|
||||
Prettier ships with a handful of customizable format options, usable in both the CLI and API.
|
||||
If you need to make modifications to the AST (such as codemods), or you want to provide an alternate parser, you can do so by setting the `parser` option to a function. The function signature of the parser function is:
|
||||
```js
|
||||
(text: string, parsers: object, options: object) => AST;
|
||||
```
|
||||
|
||||
| Option | Default | CLI override | API override |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **Print Width** - Specify the length of line that the printer will wrap on.<br /><br /><strong>We strongly recommend against using more than 80 columns</strong>. Prettier works by craming as much content as possible until it reaches the limit, which happens to work well for 80 columns but makes lines that are very crowded. When a bigger column count is used in styleguides, it usually means that code is allowed to go beyond 80 columns, but not to make every single line go there, like prettier would do. | `80` | `--print-width <int>` | `printWidth: <int>`
|
||||
| **Tab Width** - Specify the number of spaces per indentation-level. | `2` | `--tab-width <int>` | `tabWidth: <int>` |
|
||||
| **Tabs** - Indent lines with tabs instead of spaces. | `false` | `--use-tabs` | `useTabs: <bool>` |
|
||||
| **Semicolons** - Print semicolons at the ends of statements.<br /><br />Valid options: <br /> - `true` - add a semicolon at the end of every statement <br /> - `false` - only add semicolons at the beginning of lines that may introduce ASI failures | `true` | `--no-semi` | `semi: <bool>` |
|
||||
| **Quotes** - Use single quotes instead of double quotes.<br /><br />Notes:<br /> - Quotes in JSX will always be double and ignore this setting. <br /> - If the number of quotes outweighs the other quote, the quote which is less used will be used to format the string - Example: `"I'm double quoted"` results in `"I'm double quoted"` and `"This \"example\" is single quoted"` results in `'This "example" is single quoted'`. | `false` | `--single-quote` | `singleQuote: <bool>` |
|
||||
| **Trailing Commas** - Print trailing commas wherever possible.<br /><br />Valid options: <br /> - `"none"` - no trailing commas <br /> - `"es5"` - trailing commas where valid in ES5 (objects, arrays, etc) <br /> - `"all"` - trailing commas wherever possible (function arguments). This requires node 8 or a [transform](https://babeljs.io/docs/plugins/syntax-trailing-function-commas/). | `"none"` | <code>--trailing-comma <none|es5|all></code> | <code>trailingComma: "<none|es5|all>"</code> |
|
||||
| **Bracket Spacing** - Print spaces between brackets in object literals.<br /><br />Valid options: <br /> - `true` - Example: `{ foo: bar }` <br /> - `false` - Example: `{foo: bar}` | `true` | `--no-bracket-spacing` | `bracketSpacing: <bool>` |
|
||||
| **JSX Brackets on Same Line** - Put the `>` of a multi-line JSX element at the end of the last line instead of being alone on the next line | `false` | `--jsx-bracket-same-line` | `jsxBracketSameLine: <bool>` |
|
||||
| **Cursor Offset** - Specify where the cursor is. This option only works with `prettier.formatWithCursor`, and cannot be used with `rangeStart` and `rangeEnd`. | `-1` | `--cursor-offset <int>` | `cursorOffset: <int>` |
|
||||
| **Range Start** - Format code starting at a given character offset. The range will extend backwards to the start of the first line containing the selected statement. This option cannot be used with `cursorOffset`. | `0` | `--range-start <int>` | `rangeStart: <int>` |
|
||||
| **Range End** - Format code ending at a given character offset (exclusive). The range will extend forwards to the end of the selected statement. This option cannot be used with `cursorOffset`. | `Infinity` | `--range-end <int>` | `rangeEnd: <int>` |
|
||||
| **Parser** - Specify which parser to use. Both the `babylon` and `flow` parsers support the same set of JavaScript features (including Flow). Prettier automatically infers the parser from the input file path, so you shouldn't have to change this setting. | `babylon` | <code>--parser <flow|babylon|typescript|postcss></code> | <code>parser: "<flow|babylon|typescript|postcss>"</code> |
|
||||
| **Filepath** - Specify the input filepath this will be used to do parser inference.<br /><br /> Example: <br />`cat foo \| prettier --stdin-filepath foo.css`<br /> will default to use `postcss` parser | | `--stdin-filepath` | `filepath: <string>` |
|
||||
Prettier's built-in parsers are exposed as properties on the `parsers` argument.
|
||||
|
||||
|
||||
##### Example
|
||||
|
||||
```js
|
||||
prettier.format("lodash ( )", {
|
||||
parser(text, { babylon }) {
|
||||
const ast = babylon(text);
|
||||
ast.program.body[0].expression.callee.name = "_";
|
||||
return ast;
|
||||
}
|
||||
}); // ==> "_();\n"
|
||||
```
|
||||
|
||||
The `--parser` CLI option may be a path to a node.js module exporting a parse function.
|
||||
|
||||
### Excluding code from formatting
|
||||
|
||||
|
|
|
@ -93,26 +93,7 @@ function getParserOption() {
|
|||
return "flow";
|
||||
}
|
||||
|
||||
if (
|
||||
value === "flow" ||
|
||||
value === "babylon" ||
|
||||
value === "typescript" ||
|
||||
value === "postcss" ||
|
||||
value === "parse5" ||
|
||||
value === "graphql"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"Ignoring unknown --" +
|
||||
optionName +
|
||||
' value, falling back to "babylon":\n' +
|
||||
' Expected "flow" or "babylon", but received: ' +
|
||||
JSON.stringify(value)
|
||||
);
|
||||
|
||||
return "babylon";
|
||||
return value;
|
||||
}
|
||||
|
||||
function getIntOption(optionName) {
|
||||
|
|
|
@ -27,6 +27,9 @@ node_modules/.bin/rollup -c scripts/build/rollup.parser.config.js --environment
|
|||
echo 'Bundling lib typescript...';
|
||||
node_modules/.bin/rollup -c scripts/build/rollup.parser.config.js --environment parser:typescript
|
||||
|
||||
echo 'Bundling lib parse5...';
|
||||
node_modules/.bin/rollup -c scripts/build/rollup.parser.config.js --environment parser:parse5
|
||||
|
||||
echo 'Bundling lib postcss...';
|
||||
# PostCSS has dependency cycles and won't work correctly with rollup :(
|
||||
./node_modules/.bin/webpack --hide-modules src/parser-postcss.js dist/parser-postcss.js
|
||||
|
@ -59,6 +62,9 @@ node_modules/.bin/rollup -c scripts/build/rollup.docs.config.js --environment fi
|
|||
echo 'Bundling docs postcss...';
|
||||
node_modules/.bin/rollup -c scripts/build/rollup.docs.config.js --environment filepath:parser-postcss.js
|
||||
|
||||
echo 'Bundling docs parse5...';
|
||||
node_modules/.bin/rollup -c scripts/build/rollup.docs.config.js --environment filepath:parser-parse5.js
|
||||
|
||||
echo;
|
||||
|
||||
## --- Misc ---
|
||||
|
@ -72,10 +78,7 @@ cp package.json dist/
|
|||
echo 'Done!'
|
||||
echo;
|
||||
echo 'How to test against dist:'
|
||||
echo ' 1) Open tests_config/run_spec.js'
|
||||
echo ' 2) add `dist/` to the require'
|
||||
echo ' 3) yarn test'
|
||||
echo " 4) Don't forget to revert tests_config/run_spec.js"
|
||||
echo ' 1) yarn test --prod'
|
||||
echo;
|
||||
echo 'How to publish:'
|
||||
echo ' 1) IMPORTANT!!! Go to dist/'
|
||||
|
|
|
@ -48,8 +48,17 @@ function normalize(options) {
|
|||
);
|
||||
}
|
||||
|
||||
const parserBackup = normalized.parser;
|
||||
if (typeof normalized.parser === "function") {
|
||||
// Delete the function from the object to pass validation.
|
||||
delete normalized.parser;
|
||||
}
|
||||
|
||||
validate(normalized, { exampleConfig, deprecatedConfig });
|
||||
|
||||
// Restore the option back to a function;
|
||||
normalized.parser = parserBackup;
|
||||
|
||||
// For backward compatibility. Deprecated in 0.0.10
|
||||
if ("useFlowParser" in normalized) {
|
||||
normalized.parser = normalized.useFlowParser ? "flow" : "babylon";
|
||||
|
|
|
@ -1,25 +1,50 @@
|
|||
"use strict";
|
||||
|
||||
function getParseFunction(opts) {
|
||||
switch (opts.parser) {
|
||||
case "flow":
|
||||
return eval("require")("./parser-flow");
|
||||
case "graphql":
|
||||
return eval("require")("./parser-graphql");
|
||||
case "parse5":
|
||||
return eval("require")("./parser-parse5");
|
||||
case "postcss":
|
||||
return eval("require")("./parser-postcss");
|
||||
case "typescript":
|
||||
return eval("require")("./parser-typescript");
|
||||
default:
|
||||
return eval("require")("./parser-babylon");
|
||||
const path = require("path");
|
||||
|
||||
const parsers = {
|
||||
get flow() {
|
||||
return eval("require")("./parser-flow");
|
||||
},
|
||||
get graphql() {
|
||||
return eval("require")("./parser-graphql");
|
||||
},
|
||||
get parse5() {
|
||||
return eval("require")("./parser-parse5");
|
||||
},
|
||||
get babylon() {
|
||||
return eval("require")("./parser-babylon");
|
||||
},
|
||||
get typescript() {
|
||||
return eval("require")("./parser-typescript");
|
||||
},
|
||||
get postcss() {
|
||||
return eval("require")("./parser-postcss");
|
||||
}
|
||||
};
|
||||
|
||||
function resolveParseFunction(opts) {
|
||||
if (typeof opts.parser === "function") {
|
||||
return opts.parser;
|
||||
}
|
||||
if (typeof opts.parser === "string") {
|
||||
if (parsers.hasOwnProperty(opts.parser)) {
|
||||
return parsers[opts.parser];
|
||||
}
|
||||
try {
|
||||
return eval("require")(path.resolve(process.cwd(), opts.parser));
|
||||
} catch (err) {
|
||||
throw new Error(`Couldn't resolve parser "${opts.parser}"`);
|
||||
}
|
||||
}
|
||||
return parsers.babylon;
|
||||
}
|
||||
|
||||
function parse(text, opts) {
|
||||
const parseFunction = resolveParseFunction(opts);
|
||||
|
||||
try {
|
||||
return getParseFunction(opts)(text);
|
||||
return parseFunction(text, parsers, opts);
|
||||
} catch (error) {
|
||||
const loc = error.loc;
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const fs = require("fs");
|
||||
const extname = require("path").extname;
|
||||
const prettier = require("../"); // change to ../dist/ to "test in prod"
|
||||
const prettier = require(isProduction ? "../dist/" : "../");
|
||||
const parser = require("../src/parser");
|
||||
const massageAST = require("../src/clean-ast.js").massageAST;
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`allows passing a string to resolve a parser 1`] = `
|
||||
"/* eslint-disable */
|
||||
bar();
|
||||
"
|
||||
`;
|
|
@ -0,0 +1,39 @@
|
|||
"use strict";
|
||||
|
||||
const prettier = require("../..");
|
||||
const runPrettier = require("../runPrettier");
|
||||
|
||||
test("allows custom parser provided as object", () => {
|
||||
const output = prettier.format("1", {
|
||||
parser(text) {
|
||||
expect(text).toEqual("1");
|
||||
return {
|
||||
type: "Literal",
|
||||
value: 2,
|
||||
raw: "2"
|
||||
};
|
||||
}
|
||||
});
|
||||
expect(output).toEqual("2");
|
||||
});
|
||||
|
||||
test("allows usage of prettier's supported parsers", () => {
|
||||
const output = prettier.format("foo ( )", {
|
||||
parser(text, parsers) {
|
||||
expect(typeof parsers.babylon).toEqual("function");
|
||||
const ast = parsers.babylon(text);
|
||||
ast.program.body[0].expression.callee.name = "bar";
|
||||
return ast;
|
||||
}
|
||||
});
|
||||
expect(output).toEqual("bar();\n");
|
||||
});
|
||||
|
||||
test("allows passing a string to resolve a parser", () => {
|
||||
const output = runPrettier("./custom-parsers/", [
|
||||
"./custom-rename-input.js",
|
||||
"--parser",
|
||||
"./custom-rename-parser"
|
||||
]);
|
||||
expect(output.stdout).toMatchSnapshot();
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
/* eslint-disable */
|
||||
foo ( )
|
|
@ -0,0 +1,7 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function(text, parsers) {
|
||||
const ast = parsers.babylon(text);
|
||||
ast.program.body[0].expression.callee.name = "bar";
|
||||
return ast;
|
||||
};
|
Loading…
Reference in New Issue