diff --git a/changelog_unreleased/api/13250.md b/changelog_unreleased/api/13250.md
new file mode 100644
index 000000000..25e8ffd46
--- /dev/null
+++ b/changelog_unreleased/api/13250.md
@@ -0,0 +1,3 @@
+#### [BREAKING] Removed support for custom parser api (#13250 by @fisker and @thorn0)
+
+Before [plugins](/docs/en/plugins.html) were a thing, Prettier had a similar but more limited feature called custom parsers. It’s been removed in v3.0.0 as its functionality was a subset of what the Plugin API did. If you used it, please check [how to migrate](/docs/en/api.html#custom-parser-api).
diff --git a/cspell.json b/cspell.json
index 76f513357..2690ee562 100644
--- a/cspell.json
+++ b/cspell.json
@@ -156,6 +156,7 @@
"Joar",
"josephfrazier",
"jscodefmt",
+ "jscodeshift",
"jsesc",
"jsfmt",
"jsonata",
diff --git a/docs/api.md b/docs/api.md
index fb9f41d65..08278a267 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -126,18 +126,20 @@ The support information looks like this:
}
```
-## Custom Parser 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:
+## Custom Parser API (removed)
+
+_Removed in v3.0.0 (superseded by the Plugin API)_
+
+Before [plugins](plugins.md) were a thing, Prettier had a similar but more limited feature called custom parsers. It’s been removed in v3.0.0 as its functionality was a subset of what the Plugin API did. If you used it, please check the example below on how to migrate.
+
+❌ Custom parser API (removed):
```js
-(text: string, parsers: object, options: object) => AST;
-```
+import { format } from "prettier";
-Prettier’s built-in parsers are exposed as properties on the `parsers` argument.
-
-```js
-prettier.format("lodash ( )", {
+format("lodash ( )", {
parser(text, { babel }) {
const ast = babel(text);
ast.program.body[0].expression.callee.name = "_";
@@ -147,4 +149,32 @@ prettier.format("lodash ( )", {
// -> "_();\n"
```
-The `--parser` CLI option may be a path to a node.js module exporting a parse function.
+✔️ Plugin API:
+
+```js
+import { format } from "prettier";
+import parserBabel from "prettier/parser-babel.js";
+
+const myCustomPlugin = {
+ parsers: {
+ "my-custom-parser": {
+ async parse(text) {
+ const ast = await parserBabel.parsers.babel.parse(text);
+ ast.program.body[0].expression.callee.name = "_";
+ return ast;
+ },
+ astFormat: "estree",
+ },
+ },
+};
+
+await format("lodash ( )", {
+ parser: "my-custom-parser",
+ plugins: [myCustomPlugin],
+});
+// -> "_();\n"
+```
+
+> Note: Overall, doing codemods this way isn’t recommended. Prettier uses the location data of AST nodes for many things like preserving blank lines and attaching comments. When the AST is modified after the parsing, the location data often gets out of sync, which may lead to unpredictable results. Consider using [jscodeshift](https://github.com/facebook/jscodeshift) if you need codemods.
+
+As part of the removed Custom parser API, it was previously possible to pass a path to a module exporting a `parse` function via the `--parser` option. Use the `--plugin` CLI option or the `plugins` API option instead to [load plugins](plugins.md#using-plugins).
diff --git a/docs/options.md b/docs/options.md
index 91b462831..0358a959e 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -281,14 +281,14 @@ Valid options:
- `"lwc"` (same parser as `"html"`, but also formats LWC-specific syntax for unquoted template attributes) _First available in 1.17.0_
- `"yaml"` (via [yaml](https://github.com/eemeli/yaml) and [yaml-unist-parser](https://github.com/ikatyang/yaml-unist-parser)) _First available in 1.14.0_
-[Custom parsers](api.md#custom-parser-api) are also supported. _First available in v1.5.0_
-
-| Default | CLI Override | API Override |
-| ------- | ----------------------------------------------- | ---------------------------------------------------------- |
-| None | `--parser `
`--parser ./my-parser` | `parser: ""`
`parser: require("./my-parser")` |
+| Default | CLI Override | API Override |
+| ------- | ------------------- | -------------------- |
+| None | `--parser ` | `parser: ""` |
Note: the default value was `"babylon"` until v1.13.0.
+Note: the Custom parser API has been removed in v3.0.0. Use [plugins](plugins.md) instead ([how to migrate](api.md#custom-parser-api)).
+
## File Path
diff --git a/scripts/build/config.mjs b/scripts/build/config.mjs
index 7493cc2e9..6b7aa91e8 100644
--- a/scripts/build/config.mjs
+++ b/scripts/build/config.mjs
@@ -1,7 +1,6 @@
import path from "node:path";
import { createRequire } from "node:module";
import createEsmUtils from "esm-utils";
-import { PROJECT_ROOT } from "../utils/index.mjs";
const { require, dirname } = createEsmUtils(import.meta);
@@ -324,10 +323,6 @@ const coreBundles = [
path: path.join(dirname, "./shims/chalk.js"),
},
replaceDiffPackageEntry("lib/diff/array.js"),
- {
- module: path.join(PROJECT_ROOT, "src/main/load-parser.js"),
- text: "export default () => {};",
- },
],
},
{
diff --git a/src/main/load-parser.js b/src/main/load-parser.js
deleted file mode 100644
index f2dc2f004..000000000
--- a/src/main/load-parser.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// This module only works in Node.js
-// Will be replaced when bundling `standalone.js`
-
-import { createRequire } from "node:module";
-import path from "node:path";
-import { ConfigError } from "../common/errors.js";
-import { locStart, locEnd } from "../language-js/loc.js";
-
-const require = createRequire(import.meta.url);
-
-function requireParser(parser) {
- try {
- return {
- parse: require(path.resolve(process.cwd(), parser)),
- astFormat: "estree",
- locStart,
- locEnd,
- };
- } catch {
- /* istanbul ignore next */
- throw new ConfigError(`Couldn't resolve parser "${parser}"`);
- }
-}
-
-export default requireParser;
diff --git a/src/main/parser.js b/src/main/parser.js
index 6e24e4f52..c5a84e839 100644
--- a/src/main/parser.js
+++ b/src/main/parser.js
@@ -1,7 +1,5 @@
import { createRequire } from "node:module";
import { ConfigError } from "../common/errors.js";
-import { locStart, locEnd } from "../language-js/loc.js";
-import loadParser from "./load-parser.js";
const require = createRequire(import.meta.url);
@@ -26,30 +24,16 @@ function getParsers(options) {
return parsers;
}
-function resolveParser(opts, parsers = getParsers(opts)) {
- if (typeof opts.parser === "function") {
- // Custom parser API always works with JavaScript.
- return {
- parse: opts.parser,
- astFormat: "estree",
- locStart,
- locEnd,
- };
+function resolveParser(options, parsers = getParsers(options)) {
+ if (Object.prototype.hasOwnProperty.call(parsers, options.parser)) {
+ return parsers[options.parser];
}
- if (typeof opts.parser === "string") {
- if (Object.prototype.hasOwnProperty.call(parsers, opts.parser)) {
- return parsers[opts.parser];
- }
-
- /* istanbul ignore next */
- if (process.env.PRETTIER_TARGET === "universal") {
- throw new ConfigError(
- `Couldn't resolve parser "${opts.parser}". Parsers must be explicitly added to the standalone bundle.`
- );
- }
-
- return loadParser(opts.parser);
+ /* istanbul ignore next */
+ if (process.env.PRETTIER_TARGET === "universal") {
+ throw new ConfigError(
+ `Couldn't resolve parser "${options.parser}". Parsers must be explicitly added to the standalone bundle.`
+ );
}
}
diff --git a/tests/integration/__tests__/__snapshots__/parser-api.js.snap b/tests/integration/__tests__/__snapshots__/parser-api.js.snap
deleted file mode 100644
index 4ee558265..000000000
--- a/tests/integration/__tests__/__snapshots__/parser-api.js.snap
+++ /dev/null
@@ -1,11 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`allows passing a string to resolve a parser (stderr) 1`] = `""`;
-
-exports[`allows passing a string to resolve a parser (stdout) 1`] = `
-"/* eslint-disable */
-bar();
-"
-`;
-
-exports[`allows passing a string to resolve a parser (write) 1`] = `[]`;
diff --git a/tests/integration/__tests__/parser-api.js b/tests/integration/__tests__/parser-api.js
deleted file mode 100644
index 5d84e8be3..000000000
--- a/tests/integration/__tests__/parser-api.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import prettier from "../../config/prettier-entry.js";
-
-test("allows custom parser provided as object", async () => {
- const output = await prettier.format("1", {
- parser(text) {
- expect(text).toBe("1");
- return {
- type: "Literal",
- value: 2,
- raw: "2",
- };
- },
- });
- expect(output).toBe("2");
-});
-
-test("allows usage of prettier's supported parsers", async () => {
- const output = await prettier.format("foo ( )", {
- parser(text, parsers) {
- expect(typeof parsers.babel).toBe("function");
- const ast = parsers.babel(text);
- ast.program.body[0].expression.callee.name = "bar";
- return ast;
- },
- });
- expect(output).toBe("bar();\n");
-});
-
-test("parsers should allow omit optional arguments", async () => {
- let parsers;
- try {
- await prettier.format("{}", {
- parser(text, builtinParsers) {
- parsers = builtinParsers;
- },
- });
- } catch {
- // noop
- }
-
- expect(typeof parsers.babel).toBe("function");
- const code = {
- graphql: "type A {hero: Character}",
- default: "{}",
- };
- for (const [name, parse] of Object.entries(parsers)) {
- // Private parser should not be used by users
- if (name.startsWith("__")) {
- continue;
- }
-
- expect(() => parse(code[name] || code.default)).not.toThrow();
- }
-});
-
-test("allows add empty `trailingComments` array", async () => {
- const output = await prettier.format("(foo /* comment */)( )", {
- parser(text, parsers) {
- const ast = parsers.babel(text);
-
- // This may happens in this use case https://github.com/prettier/prettier/pull/5497#issuecomment-439841965
- // Simply simulate it
- const callExpression = ast.program.body[0].expression;
- callExpression.extra = { parenthesized: true };
- callExpression.trailingComments = [];
-
- return ast;
- },
- });
- expect(output).toBe("foo(/* comment */);\n");
-});
-
-describe("allows passing a string to resolve a parser", () => {
- runPrettier("./custom-parsers/", [
- "--end-of-line",
- "lf",
- "./custom-rename-input.js",
- "--parser",
- "./custom-rename-parser.cjs",
- ]).test({
- status: 0,
- });
-});
diff --git a/tests/integration/custom-parsers/custom-rename-input.js b/tests/integration/custom-parsers/custom-rename-input.js
deleted file mode 100644
index 9d954e2ad..000000000
--- a/tests/integration/custom-parsers/custom-rename-input.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/* eslint-disable */
-foo ( )
diff --git a/tests/integration/custom-parsers/custom-rename-parser.cjs b/tests/integration/custom-parsers/custom-rename-parser.cjs
deleted file mode 100644
index 900161184..000000000
--- a/tests/integration/custom-parsers/custom-rename-parser.cjs
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = function (text, parsers) {
- const ast = parsers.babel(text);
- ast.program.body[0].expression.callee.name = "bar";
- return ast;
-};