prettier: [V3] Plugins can no longer override built-in parsers

Environments:

  • Prettier Version: 3.0.0-alpha.3
  • Running Prettier via: Node.js API
  • Runtime: Node 16, 18
  • Operating System: macOS
  • Prettier plugins (if any): @ianvs/prettier-plugin-sort-imports

Steps to reproduce:

I maintain a fork of a popular prettier plugin (https://github.com/trivago/prettier-plugin-sort-imports is the original), and we both have this issue, as does https://github.com/tailwindlabs/prettier-plugin-tailwindcss and any other plugin which adds a preprocess to built-in parsers. They all break in prettier 3.0.

I traced it down to a change in the way that parsers are chosen in resolveParser. Previously, the “last” plugin in the loop which defined a parser for a given name would win. Since custom plugins come at the end of the plugin array, this meant they were able to override built-in parsers. But now, by using a for .. of loop with an early exit, the first plugin defining a parser will be used, giving plugins no chance to override or extend them.

Expected behavior:

Plugins should continue to have a way to extend built-in parsers. Or, if we need to define our own custom parsers, we need a way to determine which parser would have been chosen by prettier, so that users do not need to explicitly map their file types to our custom parsers. But again, ideally the previous behavior would be maintained.

Actual behavior:

Custom plugins cannot extend built-in parsers.

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 9
  • Comments: 26 (13 by maintainers)

Most upvoted comments

Hi, just checking in, has there been any more thought into how this can be handled in V3?

Can we please, please get a second category of plugins, e.g. AST Plugins instead of having to override parsers?

Plugins that override parsers seem to be a hack, effectively using a system that was designed to provide additional syntaxes to inject their own logic. This comes with several drawbacks:

  • Since plugins are overriding built in parsers, there can only ever be one of them, …
  • unless the plugin itself makes sure to call preprocessors of other plugins, like prettier-plugin-multiline-arrays does (finding other plugins, calling them).
  • Plugins using preprocess means that source code gets parsed and turned back into a string multiple times. This is bad for speed.
  • Plugins import modules from prettier/…, effectively requiring them to be colocated with the prettier version that calls them.
  • Plugins import the parsers they know about (like "babel" and "typescript", instead of working for a category of parser, e.g. the ones producing a specific AST format like "estree".

AST-based plugins would get an opportunity to modify the AST of a source file after it has been parsed, and prettier itself would make sure that plugins are called in the configured order without being able to override anything.

That should address the aforementioned drawbacks and offer more flexibility.

I would love to contribute some plugins for logic that is better covered by a code formatter than a linter, but the current way these are implemented is a “very creative use” of Prettier’s plugin API that was meant for something entirely different.

Released v3.0.0-alpha.4 with #13732

No worries! I know 3.0 is still in alpha, and you’re looking for feedback from plugin authors, so I figured I’d bring this up. I think it’s a great idea to take a small step back and think about an opportunity to make bigger changes in a major release, rather than just restoring the old, accidentally kind-of-working approach we were relying on. 😃

Let’s say if we freeze AST, it won’t be a breaking change.

You may not intend for it to be a breaking change, but it will break a number of plugins which modify the AST that have hundreds of thousands of weekly npm downloads (or more). Since it is apparent that this is a useful hack for prettier plugins, are you certain it’s not worth finding a way to pave the cowpath and formally allow such plugins?

Thanks for taking the time to explain.

Yes, search options.originalText

Gonna do that now.

Of course, you can, but that’s supposed to use before/after parse or print plugin’s own AST, not the bultin ones (or other plugins).

I would argue the same is true for plugin parsers. It looks as if the idea never was to allow existing ones to be overridden.

Either way, even if AST-based plugins don’t seem feasible, I think the original point still stands: It would be nice to have a plugin system that allows augmenting existing functionality, rather than using the extension hooks in ways they weren’t intended to use.

That way, prettier could have a composable plugin system where plugins can coexist without having to do runtime inspection of other plugins and built-ins.

Look at all the trickery the aforementioned multiline-arrays goes through to make sure its printer is picked. Wouldn’t it be much nicer to have a blessed way to do all this?

To unblock your migration, I’m going to temporarily restore the behavior of this function until we find a better solution. #13732

Please be patience, need more time to think about it.

Yeah, I thought about this before https://github.com/prettier/prettier/issues/10803, maybe it’s a good opportunity to implement.

Thanks. If you’re thinking about it, finding a way to support multiple plugins at the same time would be icing on the cake. See https://github.com/tailwindlabs/prettier-plugin-tailwindcss/issues/31 for an example of that issue. (https://github.com/prettier/prettier/issues/12807 in this repo as well)

Thanks for the feedback. Let’s see if there is a better way to support this use case.