1- import type { Formatter } from "./common.ts" ;
1+ import type { ConfigurationDiagnostic , FormatRequest , Formatter , GlobalConfiguration } from "./common.ts" ;
22import * as v3 from "./v3.ts" ;
33import * as v4 from "./v4.ts" ;
44
@@ -12,6 +12,195 @@ export type {
1212 PluginInfo ,
1313} from "./common.ts" ;
1414
15+ /** A registered plugin with its configuration. */
16+ interface RegisteredPlugin {
17+ formatter : Formatter ;
18+ pluginConfig : Record < string , unknown > ;
19+ fileExtensions : Set < string > ;
20+ fileNames : Set < string > ;
21+ }
22+
23+ /** A formatter returned from adding a plugin to a context. */
24+ export interface ContextFormatter {
25+ /** Formats the specified file text using this plugin. */
26+ formatText ( request : FormatRequest ) : string ;
27+ /** Gets the resolved configuration for this plugin. */
28+ getResolvedConfig ( ) : Record < string , unknown > ;
29+ /** Gets the configuration diagnostics for this plugin. */
30+ getConfigDiagnostics ( ) : ConfigurationDiagnostic [ ] ;
31+ }
32+
33+ /** A context for managing multiple formatters with shared configuration. */
34+ export interface FormatterContext {
35+ /**
36+ * Adds a plugin to the context.
37+ * @param source - The buffer or Wasm module of the plugin (e.g., from `@dprint/json` getBuffer()).
38+ * @param param - Plugin config.
39+ * @returns A formatter for directly formatting with this plugin.
40+ */
41+ addPlugin (
42+ source : BufferSource | WebAssembly . Module ,
43+ pluginConfig ?: Record < string , unknown > ,
44+ ) : ContextFormatter ;
45+
46+ /**
47+ * Adds a plugin to the context.
48+ * @param source - Source response object.
49+ * @param pluginConfig - Plugin config.
50+ * @returns A formatter for directly formatting with this plugin.
51+ */
52+ addPluginStreaming (
53+ source : ResponseLike ,
54+ pluginConfig ?: Record < string , unknown > ,
55+ ) : Promise < ContextFormatter > ;
56+
57+ /**
58+ * Formats the specified file text, automatically selecting the appropriate plugin.
59+ * @param request - Data to format.
60+ * @returns The formatted text.
61+ * @throws If no plugin matches the file or there is an error formatting.
62+ */
63+ formatText ( request : FormatRequest ) : string ;
64+
65+ /**
66+ * Gets all configuration diagnostics from all plugins.
67+ */
68+ getConfigDiagnostics ( ) : ConfigurationDiagnostic [ ] ;
69+ }
70+
71+ /**
72+ * Creates a formatter context for managing multiple plugins with shared configuration.
73+ * @param globalConfig - Global configuration shared across all plugins.
74+ */
75+ export function createContext ( globalConfig : GlobalConfiguration = { } ) : FormatterContext {
76+ const plugins : RegisteredPlugin [ ] = [ ] ;
77+
78+ function findPluginForFile ( filePath : string ) : RegisteredPlugin | undefined {
79+ const fileName = getFileName ( filePath ) ;
80+ const ext = getFileExtension ( filePath ) ;
81+
82+ // First try to match by exact file name
83+ for ( const plugin of plugins ) {
84+ if ( plugin . fileNames . has ( fileName ) ) {
85+ return plugin ;
86+ }
87+ }
88+
89+ // Then try to match by extension
90+ if ( ext ) {
91+ for ( const plugin of plugins ) {
92+ if ( plugin . fileExtensions . has ( ext ) ) {
93+ return plugin ;
94+ }
95+ }
96+ }
97+
98+ return undefined ;
99+ }
100+
101+ function createHostFormatter (
102+ currentPlugin : RegisteredPlugin ,
103+ ) : ( request : FormatRequest ) => string {
104+ return ( request : FormatRequest ) => {
105+ const plugin = findPluginForFile ( request . filePath ) ;
106+ if ( plugin && plugin !== currentPlugin ) {
107+ return plugin . formatter . formatText ( request ) ;
108+ }
109+ // Return unchanged if no other plugin matches
110+ return request . fileText ;
111+ } ;
112+ }
113+
114+ return {
115+ async addPluginStreaming ( source : ResponseLike , pluginConfig ?: Record < string , unknown > ) {
116+ const wasmModule = await createWasmModuleFromStreaming ( source ) ;
117+ return this . addPlugin ( wasmModule , pluginConfig ) ;
118+ } ,
119+ addPlugin (
120+ source : BufferSource | WebAssembly . Module ,
121+ pluginConfig : Record < string , unknown > = { } ,
122+ ) : ContextFormatter {
123+ const formatter = source instanceof WebAssembly . Module
124+ ? createFromWasmModule ( source )
125+ : createFromBuffer ( source ) ;
126+
127+ // Set configuration
128+ formatter . setConfig ( globalConfig , pluginConfig ) ;
129+
130+ // Get file matching info
131+ const matchingInfo = formatter . getFileMatchingInfo ( ) ;
132+ const fileExtensions = new Set (
133+ matchingInfo . fileExtensions . map ( ( ext ) => ext . toLowerCase ( ) ) ,
134+ ) ;
135+ const fileNames = new Set (
136+ matchingInfo . fileNames . map ( ( name ) => name . toLowerCase ( ) ) ,
137+ ) ;
138+
139+ const registered : RegisteredPlugin = {
140+ formatter,
141+ pluginConfig,
142+ fileExtensions,
143+ fileNames,
144+ } ;
145+
146+ plugins . push ( registered ) ;
147+
148+ // Set up host formatter for this plugin
149+ formatter . setHostFormatter ( createHostFormatter ( registered ) ) ;
150+
151+ // Return a context-aware formatter
152+ return {
153+ formatText ( request : FormatRequest ) : string {
154+ return formatter . formatText ( request ) ;
155+ } ,
156+ getResolvedConfig ( ) : Record < string , unknown > {
157+ return formatter . getResolvedConfig ( ) ;
158+ } ,
159+ getConfigDiagnostics ( ) : ConfigurationDiagnostic [ ] {
160+ return formatter . getConfigDiagnostics ( ) ;
161+ } ,
162+ } ;
163+ } ,
164+
165+ formatText ( request : FormatRequest ) : string {
166+ const plugin = findPluginForFile ( request . filePath ) ;
167+ if ( ! plugin ) {
168+ throw new Error (
169+ `No plugin found for file: ${ request . filePath } . `
170+ + `Registered plugins handle: ${
171+ plugins
172+ . map ( ( p ) => [ ...p . fileExtensions ] . join ( ", " ) )
173+ . join ( "; " )
174+ } `,
175+ ) ;
176+ }
177+ return plugin . formatter . formatText ( request ) ;
178+ } ,
179+
180+ getConfigDiagnostics ( ) : ConfigurationDiagnostic [ ] {
181+ return plugins . flatMap ( ( p ) => p . formatter . getConfigDiagnostics ( ) ) ;
182+ } ,
183+ } ;
184+ }
185+
186+ function getFileName ( filePath : string ) : string {
187+ const lastSlash = Math . max (
188+ filePath . lastIndexOf ( "/" ) ,
189+ filePath . lastIndexOf ( "\\" ) ,
190+ ) ;
191+ return ( lastSlash >= 0 ? filePath . slice ( lastSlash + 1 ) : filePath )
192+ . toLowerCase ( ) ;
193+ }
194+
195+ function getFileExtension ( filePath : string ) : string | undefined {
196+ const fileName = getFileName ( filePath ) ;
197+ const lastDot = fileName . lastIndexOf ( "." ) ;
198+ if ( lastDot > 0 ) {
199+ return fileName . slice ( lastDot + 1 ) ;
200+ }
201+ return undefined ;
202+ }
203+
15204export interface ResponseLike {
16205 status : number ;
17206 arrayBuffer ( ) : Promise < BufferSource > ;
@@ -29,6 +218,11 @@ export interface ResponseLike {
29218export async function createStreaming (
30219 responsePromise : Promise < ResponseLike > | ResponseLike ,
31220) : Promise < Formatter > {
221+ const wasmModule = await createWasmModuleFromStreaming ( responsePromise ) ;
222+ return createFromWasmModule ( wasmModule ) ;
223+ }
224+
225+ async function createWasmModuleFromStreaming ( responsePromise : Promise < ResponseLike > | ResponseLike ) {
32226 const response = await responsePromise ;
33227 if ( response . status !== 200 ) {
34228 throw new Error (
@@ -40,12 +234,11 @@ export async function createStreaming(
40234 && response . headers . get ( "content-type" ) === "application/wasm"
41235 ) {
42236 // deno-lint-ignore no-explicit-any
43- const module = await WebAssembly . compileStreaming ( response as any ) ;
44- return createFromWasmModule ( module ) ;
237+ return await WebAssembly . compileStreaming ( response as any ) ;
45238 } else {
46239 // fallback for node.js or when the content type isn't application/wasm
47240 return response . arrayBuffer ( )
48- . then ( ( buffer ) => createFromBuffer ( buffer ) ) ;
241+ . then ( ( buffer ) => new WebAssembly . Module ( buffer ) ) ;
49242 }
50243}
51244
0 commit comments