1- import type { AstPath , Parser , ParserOptions , Printer } from 'prettier'
1+ import type { Parser , ParserOptions , Plugin , Printer } from 'prettier'
22import { getTailwindConfig } from './config'
33import { createMatcher } from './options'
4- import type { loadPlugins } from './plugins '
4+ import { loadIfExists , maybeResolve } from './resolve '
55import type { TransformOptions } from './transform'
6- import type { Customizations , TransformerEnv , TransformerMetadata } from './types'
6+ import type { TransformerEnv } from './types'
77
8- type Base = Awaited < ReturnType < typeof loadPlugins > >
9-
10- export function createPlugin ( base : Base , transforms : TransformOptions < any > [ ] ) {
8+ export function createPlugin ( transforms : TransformOptions < any > [ ] ) {
119 // Prettier parsers and printers may be async functions at definition time.
1210 // They'll be awaited when the plugin is loaded but must also be swapped out
1311 // with the resolved value before returning as later Prettier internals
@@ -20,9 +18,13 @@ export function createPlugin(base: Base, transforms: TransformOptions<any>[]) {
2018 for ( let opts of transforms ) {
2119 for ( let [ name , meta ] of Object . entries ( opts . parsers ) ) {
2220 parsers [ name ] = async ( ) => {
23- parsers [ name ] = createParser ( {
24- base,
25- parserFormat : name ,
21+ let plugin = await loadPlugins ( meta . load ?? opts . load ?? [ ] )
22+ let original = plugin . parsers ?. [ name ]
23+ if ( ! original ) return
24+
25+ parsers [ name ] = await createParser ( {
26+ name,
27+ original,
2628 opts,
2729 } )
2830
@@ -32,9 +34,12 @@ export function createPlugin(base: Base, transforms: TransformOptions<any>[]) {
3234
3335 for ( let [ name , meta ] of Object . entries ( opts . printers ?? { } ) ) {
3436 printers [ name ] = async ( ) => {
37+ let plugin = await loadPlugins ( opts . load ?? [ ] )
38+ let original = plugin . printers ?. [ name ]
39+ if ( ! original ) return
40+
3541 printers [ name ] = createPrinter ( {
36- base,
37- name,
42+ original,
3843 opts,
3944 } )
4045
@@ -46,29 +51,48 @@ export function createPlugin(base: Base, transforms: TransformOptions<any>[]) {
4651 return { parsers, printers }
4752}
4853
49- function createParser ( {
50- //
51- base,
52- parserFormat,
54+ async function createParser ( {
55+ name,
56+ original,
5357 opts,
5458} : {
55- base : Base
56- parserFormat : string
59+ name : string
60+ original : Parser < any >
5761 opts : TransformOptions < any >
5862} ) {
59- let original = base . parsers [ parserFormat ]
6063 let parser : Parser < any > = { ...original }
6164
65+ let compatible : { pluginName : string ; mod : unknown } [ ] = [ ]
66+
67+ for ( let pluginName of opts . compatible ?? [ ] ) {
68+ let mod = await loadIfExistsESM ( pluginName )
69+ compatible . push ( { pluginName, mod } )
70+ }
71+
72+ function load ( options : ParserOptions < any > ) {
73+ let parser : Parser < any > = { ...original }
74+
75+ for ( let { pluginName, mod } of compatible ) {
76+ let plugin = findEnabledPlugin ( options , pluginName , mod )
77+ if ( plugin ) Object . assign ( parser , plugin . parsers [ name ] )
78+ }
79+
80+ return parser
81+ }
82+
6283 parser . preprocess = ( code : string , options : ParserOptions ) => {
63- let original = base . originalParser ( parserFormat , options )
84+ let parser = load ( options )
6485
65- return original . preprocess ? original . preprocess ( code , options ) : code
86+ return parser . preprocess ? parser . preprocess ( code , options ) : code
6687 }
6788
68- parser . parse = async ( code : string , options : ParserOptions ) => {
69- let original = base . originalParser ( parserFormat , options )
89+ parser . parse = async ( code , options ) => {
90+ let original = load ( options )
7091
71- // @ts -ignore: We pass three options in the case of plugins that support Prettier 2 _and_ 3.
92+ // @ts -expect-error: `options` is passed twice for compat with older plugins that were written
93+ // for Prettier v2 but still work with v3.
94+ //
95+ // Currently only the Twig plugin requires this.
7296 let ast = await original . parse ( code , options , options )
7397
7498 let env = await loadTailwindCSS ( { opts, options } )
@@ -88,20 +112,12 @@ function createParser({
88112 return parser
89113}
90114
91- function createPrinter ( {
92- //
93- base,
94- name,
95- opts,
96- } : {
97- base : Base
98- name : string
99- opts : TransformOptions < any >
100- } ) : Printer < any > {
101- let original = base . printers [ name ]
102- let printer = { ...original }
115+ function createPrinter ( { original, opts } : { original : Printer < any > ; opts : TransformOptions < any > } ) {
116+ let printer : Printer < any > = { ...original }
117+
103118 let reprint = opts . reprint
104119
120+ // Hook into the preprocessing phase to load the config
105121 if ( reprint ) {
106122 printer . print = new Proxy ( original . print , {
107123 apply ( target , thisArg , args ) {
@@ -127,6 +143,83 @@ function createPrinter({
127143 return printer
128144}
129145
146+ async function loadPlugins < T > ( fns : string [ ] ) {
147+ let plugin : Plugin < T > = {
148+ parsers : Object . create ( null ) ,
149+ printers : Object . create ( null ) ,
150+ options : Object . create ( null ) ,
151+ defaultOptions : Object . create ( null ) ,
152+ languages : [ ] ,
153+ }
154+
155+ for ( let moduleName of fns ) {
156+ try {
157+ let loaded = await loadIfExistsESM ( moduleName )
158+ Object . assign ( plugin . parsers ! , loaded . parsers ?? { } )
159+ Object . assign ( plugin . printers ! , loaded . printers ?? { } )
160+ Object . assign ( plugin . options ! , loaded . options ?? { } )
161+ Object . assign ( plugin . defaultOptions ! , loaded . defaultOptions ?? { } )
162+
163+ plugin . languages = [ ...( plugin . languages ?? [ ] ) , ...( loaded . languages ?? [ ] ) ]
164+ } catch ( err ) {
165+ throw err
166+ }
167+ }
168+
169+ return plugin
170+ }
171+
172+ async function loadIfExistsESM ( name : string ) : Promise < Plugin < any > > {
173+ let mod = await loadIfExists < Plugin < any > > ( name )
174+
175+ return (
176+ mod ?? {
177+ parsers : { } ,
178+ printers : { } ,
179+ languages : [ ] ,
180+ options : { } ,
181+ defaultOptions : { } ,
182+ }
183+ )
184+ }
185+
186+ function findEnabledPlugin ( options : ParserOptions < any > , name : string , mod : any ) {
187+ let path = maybeResolve ( name )
188+
189+ for ( let plugin of options . plugins ) {
190+ if ( plugin instanceof URL ) {
191+ if ( plugin . protocol !== 'file:' ) continue
192+ if ( plugin . hostname !== '' ) continue
193+
194+ plugin = plugin . pathname
195+ }
196+
197+ if ( typeof plugin === 'string' ) {
198+ if ( plugin === name || plugin === path ) {
199+ return mod
200+ }
201+
202+ continue
203+ }
204+
205+ // options.plugins.*.name == name
206+ if ( plugin . name === name ) {
207+ return mod
208+ }
209+
210+ // options.plugins.*.name == path
211+ if ( plugin . name === path ) {
212+ return mod
213+ }
214+
215+ // basically options.plugins.* == mod
216+ // But that can't work because prettier normalizes plugins which destroys top-level object identity
217+ if ( plugin . parsers && mod . parsers && plugin . parsers == mod . parsers ) {
218+ return mod
219+ }
220+ }
221+ }
222+
130223async function loadTailwindCSS < T = any > ( {
131224 options,
132225 opts,
0 commit comments