aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ide.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/ide.js')
-rw-r--r--src/ide.js324
1 files changed, 167 insertions, 157 deletions
diff --git a/src/ide.js b/src/ide.js
index f839fd5..ac80789 100644
--- a/src/ide.js
+++ b/src/ide.js
@@ -12,193 +12,71 @@ const spawn = require('cross-spawn');
12 12
13const colors = require('chalk'); 13const colors = require('chalk');
14 14
15const debug = require('debug')('purs-loader'); 15const debug_ = require('debug');
16 16
17const dargs = require('./dargs'); 17const debug = debug_('purs-loader');
18
19const Psc = require('./Psc');
20
21const PsModuleMap = require('./PsModuleMap');
22
23module.exports.connect = function connect(psModule) {
24 const options = psModule.options
25 const cache = psModule.cache
26
27 if (cache.ideServer) return Promise.resolve(psModule)
28 18
29 cache.ideServer = true 19const debugVerbose = debug_('purs-loader:verbose');
30 20
31 const connect_ = () => new Promise((resolve, reject) => { 21const dargs = require('./dargs');
32 const args = dargs(options.pscIdeArgs)
33
34 debug('attempting to run purs ide client: %o', args)
35 22
36 const ideClient = spawn('purs', ['ide', 'client'].concat(args)) 23const compile = require('./compile');
37 24
38 ideClient.stderr.on('data', data => { 25const PsModuleMap = require('./PsModuleMap');
39 debug(data.toString())
40 cache.ideServer = false
41 reject(new Error('purs ide client failed'))
42 })
43 ideClient.stdout.once('data', data => {
44 debug(data.toString())
45 if (data.toString()[0] === '{') {
46 const res = JSON.parse(data.toString())
47 if (res.resultType === 'success') {
48 cache.ideServer = ideServer
49 resolve(psModule)
50 } else {
51 cache.ideServer = ideServer
52 reject(new Error('purs ide client failed'))
53 }
54 } else {
55 cache.ideServer = false
56 reject(new Error('purs ide client failed'))
57 }
58 })
59 ideClient.stdin.resume()
60 ideClient.stdin.write(JSON.stringify({ command: 'load' }))
61 ideClient.stdin.write('\n')
62 })
63 26
64 const serverArgs = dargs(Object.assign({ 27function UnknownModuleError() {
65 outputDirectory: options.output, 28 this.name = 'UnknownModuleError';
66 '_': options.src 29 this.stack = (new Error()).stack;
67 }, options.pscIdeServerArgs)) 30}
68 31
69 debug('attempting to start purs ide server: %o', serverArgs) 32UnknownModuleError.prototype = Object.create(Error.prototype);
70 33
71 const ideServer = cache.ideServer = spawn('purs', ['ide', 'server'].concat(serverArgs)) 34UnknownModuleError.prototype.constructor = UnknownModuleError;
72 35
73 ideServer.stdout.on('data', data => { 36module.exports.UnknownModuleError = UnknownModuleError;
74 debug('purs ide server stdout: %s', data.toString());
75 });
76 37
77 ideServer.stderr.on('data', data => { 38function spawnIdeClient(body, options) {
78 debug('purs ide server stderr: %s', data.toString()); 39 const ideClientCommand = 'purs';
79 });
80 40
81 ideServer.on('error', error => { 41 const ideClientArgs = ['ide', 'client'].concat(dargs(options.pscIdeArgs));
82 debug('purs ide server error: %o', error);
83 });
84 42
85 ideServer.on('close', (code, signal) => { 43 const stderr = [];
86 debug('purs ide server close: %s %s', code, signal);
87 });
88 44
89 return retryPromise((retry, number) => { 45 const stdout = [];
90 return connect_().catch(error => {
91 if (!cache.ideServer && number === 9) {
92 debug(error)
93 46
94 console.warn('Failed to connect to or start purs ide server. A full compilation will occur on rebuild'); 47 debug('ide client %s %o %o', ideClientCommand, ideClientArgs, body);
95 48
96 return Promise.resolve(psModule) 49 return new Promise((resolve, reject) => {
97 } 50 const ideClient = spawn(ideClientCommand, ideClientArgs);
98 51
99 return retry(error) 52 ideClient.stderr.on('data', data => {
53 stderr.push(data.toString());
100 }) 54 })
101 }, {
102 retries: 9,
103 factor: 1,
104 minTimeout: 333,
105 maxTimeout: 333,
106 })
107};
108
109module.exports.rebuild = function rebuild(psModule) {
110 const options = psModule.options
111 const cache = psModule.cache
112
113 debug('attempting rebuild with purs ide client %s', psModule.srcPath)
114
115 const request = (body) => new Promise((resolve, reject) => {
116 const args = dargs(options.pscIdeArgs)
117 const ideClient = spawn('purs', ['ide', 'client'].concat(args))
118
119 var stdout = ''
120 var stderr = ''
121 55
122 ideClient.stdout.on('data', data => { 56 ideClient.stdout.on('data', data => {
123 stdout = stdout + data.toString() 57 stdout.push(data.toString());
124 })
125
126 ideClient.stderr.on('data', data => {
127 stderr = stderr + data.toString()
128 }) 58 })
129 59
130 ideClient.on('close', code => { 60 ideClient.on('close', code => {
131 if (code !== 0) { 61 if (code !== 0) {
132 const error = stderr === '' ? 'Failed to spawn purs ide client' : stderr 62 const errorMessage = stderr.join('');
133 return reject(new Error(error))
134 }
135
136 let res = null
137 63
138 try { 64 reject(new Error(`ide client failed: ${errorMessage}`));
139 res = JSON.parse(stdout.toString())
140 debug(res)
141 } catch (err) {
142 return reject(err)
143 } 65 }
66 else {
67 const result = stdout.join('');
144 68
145 if (res && !Array.isArray(res.result)) { 69 resolve(result);
146 return resolve(psModule);
147 } 70 }
148
149 Promise.map(res.result, (item, i) => {
150 debug(item)
151 return formatIdeResult(item, options, i, res.result.length)
152 })
153 .then(compileMessages => {
154 if (res.resultType === 'error') {
155 if (res.result.some(item => {
156 const isModuleNotFound = item.errorCode === 'ModuleNotFound';
157
158 const isUnknownModule = item.errorCode === 'UnknownModule';
159
160 const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message);
161
162 return isModuleNotFound || isUnknownModule || isUnknownModuleImport;
163 })) {
164 debug('unknown module, attempting full recompile')
165 return Psc.compile(psModule)
166 .then(() => PsModuleMap.makeMap(options.src).then(map => {
167 debug('rebuilt module map after unknown module forced a recompile');
168 cache.psModuleMap = map;
169 }))
170 .then(() => request({ command: 'load' }))
171 .then(resolve)
172 .catch(() => resolve(psModule))
173 }
174 const errorMessage = compileMessages.join('\n');
175 if (errorMessage.length) {
176 psModule.emitError(errorMessage);
177 }
178 resolve(psModule);
179 } else {
180 const warningMessage = compileMessages.join('\n');
181 if (options.warnings && warningMessage.length) {
182 psModule.emitWarning(warningMessage);
183 }
184 resolve(psModule);
185 }
186 })
187 }) 71 })
188 72
189 debug('purs ide client stdin: %o', body); 73 ideClient.stdin.resume();
190 74
191 ideClient.stdin.write(JSON.stringify(body)) 75 ideClient.stdin.write(JSON.stringify(body));
192 ideClient.stdin.write('\n')
193 })
194 76
195 return request({ 77 ideClient.stdin.write('\n');
196 command: 'rebuild', 78 });
197 params: { 79}
198 file: psModule.srcPath,
199 }
200 })
201};
202 80
203function formatIdeResult(result, options, index, length) { 81function formatIdeResult(result, options, index, length) {
204 let numAndErr = `[${index+1}/${length} ${result.errorCode}]` 82 let numAndErr = `[${index+1}/${length} ${result.errorCode}]`
@@ -253,8 +131,140 @@ function formatIdeResult(result, options, index, length) {
253 } 131 }
254 132
255 return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) 133 return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`)
256 }) 134 }).catch(error => {
135 debug('failed to format ide result: %o', error);
136
137 return Promise.resolve('');
138 });
257 } 139 }
258 140
259 return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); 141 return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult();
260} 142}
143
144module.exports.connect = function connect(psModule) {
145 const options = psModule.options
146
147 const serverCommand = 'purs';
148
149 const serverArgs = ['ide', 'server'].concat(dargs(Object.assign({
150 outputDirectory: options.output,
151 '_': options.src
152 }, options.pscIdeServerArgs)));
153
154 debug('ide server: %s %o', serverCommand, serverArgs);
155
156 const ideServer = spawn(serverCommand, serverArgs);
157
158 ideServer.stdout.on('data', data => {
159 debugVerbose('ide server stdout: %s', data.toString());
160 });
161
162 ideServer.stderr.on('data', data => {
163 debugVerbose('ide server stderr: %s', data.toString());
164 });
165
166 ideServer.on('error', error => {
167 debugVerbose('ide server error: %o', error);
168 });
169
170 ideServer.on('close', (code, signal) => {
171 debugVerbose('ide server close: %s %s', code, signal);
172 });
173
174 return Promise.resolve(ideServer);
175};
176
177module.exports.load = function load(psModule) {
178 const options = psModule.options
179
180 const body = {command: 'load'};
181
182 return spawnIdeClient(body, options);
183};
184
185module.exports.loadWithRetry = function loadWithRetry(psModule) {
186 const retries = 9;
187
188 return retryPromise((retry, number) => {
189 debugVerbose('attempting to load modules (%d out of %d attempts)', number, retries);
190
191 return module.exports.load(psModule).catch(retry);
192 }, {
193 retries: retries,
194 factor: 1,
195 minTimeout: 333,
196 maxTimeout: 333,
197 }).then(() => psModule);
198};
199
200module.exports.rebuild = function rebuild(psModule) {
201 const options = psModule.options;
202
203 const body = {
204 command: 'rebuild',
205 params: {
206 file: psModule.srcPath,
207 }
208 };
209
210 const parseResponse = response => {
211 try {
212 const parsed = JSON.parse(response);
213
214 debugVerbose('parsed JSON response: %o', parsed);
215
216 return Promise.resolve(parsed);
217 }
218 catch (error) {
219 return Promise.reject(error);
220 }
221 };
222
223 const formatResponse = parsed => {
224 const result = Array.isArray(parsed.result) ? parsed.result : [];
225
226 return Promise.map(result, (item, i) => {
227 debugVerbose('formatting result %o', item);
228
229 return formatIdeResult(item, options, i, result.length);
230 }).then(formatted => ({
231 parsed: parsed,
232 formatted: formatted,
233 formattedMessage: formatted.join('\n')
234 }));
235 };
236
237 return spawnIdeClient(body, options)
238 .then(parseResponse)
239 .then(formatResponse)
240 .then(({ parsed, formatted, formattedMessage }) => {
241 if (parsed.resultType === 'success') {
242 if (options.warnings && formattedMessage.length) {
243 psModule.emitWarning(formattedMessage);
244 }
245
246 return psModule;
247 }
248 else if ((parsed.result || []).some(item => {
249 const isModuleNotFound = item.errorCode === 'ModuleNotFound';
250
251 const isUnknownModule = item.errorCode === 'UnknownModule';
252
253 const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message);
254
255 return isModuleNotFound || isUnknownModule || isUnknownModuleImport;
256 })) {
257 debug('failed to rebuild because the module is unknown')
258
259 return Promise.reject(new UnknownModuleError());
260 }
261 else {
262 if (formattedMessage.length) {
263 psModule.emitError(formattedMessage);
264 }
265
266 return psModule;
267 }
268 })
269 ;
270};