1const { Buffer } = require('buffer');
2const fs = require("fs");
3const path = require("path");
4const { once } = require('events');
5
6const prettierContainerPath = process.argv[2];
7if (prettierContainerPath == null || prettierContainerPath.length == 0) {
8 process.stderr.write(`Prettier path argument was not specified or empty.\nUsage: ${process.argv[0]} ${process.argv[1]} prettier/path\n`);
9 process.exit(1);
10}
11fs.stat(prettierContainerPath, (err, stats) => {
12 if (err) {
13 process.stderr.write(`Path '${prettierContainerPath}' does not exist\n`);
14 process.exit(1);
15 }
16
17 if (!stats.isDirectory()) {
18 process.stderr.write(`Path '${prettierContainerPath}' exists but is not a directory\n`);
19 process.exit(1);
20 }
21});
22const prettierPath = path.join(prettierContainerPath, 'node_modules/prettier');
23
24class Prettier {
25 constructor(path, prettier, config) {
26 this.path = path;
27 this.prettier = prettier;
28 this.config = config;
29 }
30}
31
32(async () => {
33 let prettier;
34 let config;
35 try {
36 prettier = await loadPrettier(prettierPath);
37 config = await prettier.resolveConfig(prettierPath) || {};
38 } catch (e) {
39 process.stderr.write(`Failed to load prettier: ${e}\n`);
40 process.exit(1);
41 }
42 process.stderr.write(`Prettier at path '${prettierPath}' loaded successfully, config: ${JSON.stringify(config)}\n`);
43 process.stdin.resume();
44 handleBuffer(new Prettier(prettierPath, prettier, config));
45})()
46
47async function handleBuffer(prettier) {
48 for await (const messageText of readStdin()) {
49 let message;
50 try {
51 message = JSON.parse(messageText);
52 } catch (e) {
53 sendResponse(makeError(`Failed to parse message '${messageText}': ${e}`));
54 continue;
55 }
56 // allow concurrent request handling by not `await`ing the message handling promise (async function)
57 handleMessage(message, prettier).catch(e => {
58 const errorMessage = message;
59 if ((errorMessage.params || {}).text !== undefined) {
60 errorMessage.params.text = "..snip..";
61 }
62 sendResponse({ id: message.id, ...makeError(`error during message '${JSON.stringify(errorMessage)}' handling: ${e}`) }); });
63 }
64}
65
66const headerSeparator = "\r\n";
67const contentLengthHeaderName = 'Content-Length';
68
69async function* readStdin() {
70 let buffer = Buffer.alloc(0);
71 let streamEnded = false;
72 process.stdin.on('end', () => {
73 streamEnded = true;
74 });
75 process.stdin.on('data', (data) => {
76 buffer = Buffer.concat([buffer, data]);
77 });
78
79 async function handleStreamEnded(errorMessage) {
80 sendResponse(makeError(errorMessage));
81 buffer = Buffer.alloc(0);
82 messageLength = null;
83 await once(process.stdin, 'readable');
84 streamEnded = false;
85 }
86
87 try {
88 let headersLength = null;
89 let messageLength = null;
90 main_loop: while (true) {
91 if (messageLength === null) {
92 while (buffer.indexOf(`${headerSeparator}${headerSeparator}`) === -1) {
93 if (streamEnded) {
94 await handleStreamEnded('Unexpected end of stream: headers not found');
95 continue main_loop;
96 } else if (buffer.length > contentLengthHeaderName.length * 10) {
97 await handleStreamEnded(`Unexpected stream of bytes: no headers end found after ${buffer.length} bytes of input`);
98 continue main_loop;
99 }
100 await once(process.stdin, 'readable');
101 }
102 const headers = buffer.subarray(0, buffer.indexOf(`${headerSeparator}${headerSeparator}`)).toString('ascii');
103 const contentLengthHeader = headers.split(headerSeparator)
104 .map(header => header.split(':'))
105 .filter(header => header[2] === undefined)
106 .filter(header => (header[1] || '').length > 0)
107 .find(header => (header[0] || '').trim() === contentLengthHeaderName);
108 const contentLength = (contentLengthHeader || [])[1];
109 if (contentLength === undefined) {
110 await handleStreamEnded(`Missing or incorrect ${contentLengthHeaderName} header: ${headers}`);
111 continue main_loop;
112 }
113 headersLength = headers.length + headerSeparator.length * 2;
114 messageLength = parseInt(contentLength, 10);
115 }
116
117 while (buffer.length < (headersLength + messageLength)) {
118 if (streamEnded) {
119 await handleStreamEnded(
120 `Unexpected end of stream: buffer length ${buffer.length} does not match expected header length ${headersLength} + body length ${messageLength}`);
121 continue main_loop;
122 }
123 await once(process.stdin, 'readable');
124 }
125
126 const messageEnd = headersLength + messageLength;
127 const message = buffer.subarray(headersLength, messageEnd);
128 buffer = buffer.subarray(messageEnd);
129 headersLength = null;
130 messageLength = null;
131 yield message.toString('utf8');
132 }
133 } catch (e) {
134 sendResponse(makeError(`Error reading stdin: ${e}`));
135 } finally {
136 process.stdin.off('data', () => { });
137 }
138}
139
140async function handleMessage(message, prettier) {
141 const { method, id, params } = message;
142 if (method === undefined) {
143 throw new Error(`Message method is undefined: ${JSON.stringify(message)}`);
144 }
145 if (id === undefined) {
146 throw new Error(`Message id is undefined: ${JSON.stringify(message)}`);
147 }
148
149 if (method === 'prettier/format') {
150 if (params === undefined || params.text === undefined) {
151 throw new Error(`Message params.text is undefined: ${JSON.stringify(message)}`);
152 }
153 if (params.options === undefined) {
154 throw new Error(`Message params.options is undefined: ${JSON.stringify(message)}`);
155 }
156
157 let resolvedConfig = {};
158 if (params.options.filepath !== undefined) {
159 resolvedConfig = await prettier.prettier.resolveConfig(params.options.filepath) || {};
160 }
161
162 const options = {
163 ...(params.options.prettierOptions || prettier.config),
164 ...resolvedConfig,
165 parser: params.options.parser,
166 plugins: params.options.plugins,
167 path: params.options.filepath
168 };
169 process.stderr.write(`Resolved config: ${JSON.stringify(resolvedConfig)}, will format file '${params.options.filepath || ''}' with options: ${JSON.stringify(options)}\n`);
170 const formattedText = await prettier.prettier.format(params.text, options);
171 sendResponse({ id, result: { text: formattedText } });
172 } else if (method === 'prettier/clear_cache') {
173 prettier.prettier.clearConfigCache();
174 prettier.config = await prettier.prettier.resolveConfig(prettier.path) || {};
175 sendResponse({ id, result: null });
176 } else if (method === 'initialize') {
177 sendResponse({
178 id: id || 0,
179 result: {
180 "capabilities": {}
181 }
182 });
183 } else {
184 throw new Error(`Unknown method: ${method}`);
185 }
186}
187
188function makeError(message) {
189 return {
190 error: {
191 "code": -32600, // invalid request code
192 message,
193 }
194 };
195}
196
197function sendResponse(response) {
198 const responsePayloadString = JSON.stringify({
199 jsonrpc: "2.0",
200 ...response
201 });
202 const headers = `${contentLengthHeaderName}: ${Buffer.byteLength(responsePayloadString)}${headerSeparator}${headerSeparator}`;
203 process.stdout.write(headers + responsePayloadString);
204}
205
206function loadPrettier(prettierPath) {
207 return new Promise((resolve, reject) => {
208 fs.access(prettierPath, fs.constants.F_OK, (err) => {
209 if (err) {
210 reject(`Path '${prettierPath}' does not exist.Error: ${err}`);
211 } else {
212 try {
213 resolve(require(prettierPath));
214 } catch (err) {
215 reject(`Error requiring prettier module from path '${prettierPath}'.Error: ${err}`);
216 }
217 }
218 });
219 });
220}