]> git.immae.eu Git - github/fretlink/purs-loader.git/blame - lib/ide.js
Build v3.3.1
[github/fretlink/purs-loader.git] / lib / ide.js
CommitLineData
d3f40b6f
CS
1'use strict';
2
3var path = require('path');
4
5var Promise = require('bluebird');
6
7var fs = Promise.promisifyAll(require('fs'));
8
9var retryPromise = require('promise-retry');
10
11var spawn = require('cross-spawn');
12
13var colors = require('chalk');
14
15var debug_ = require('debug');
16
17var debug = debug_('purs-loader');
18
19var debugVerbose = debug_('purs-loader:verbose');
20
21var dargs = require('./dargs');
22
23var compile = require('./compile');
24
25var PsModuleMap = require('./purs-module-map');
26
27function UnknownModuleError() {
28 this.name = 'UnknownModuleError';
29 this.stack = new Error().stack;
30}
31
32UnknownModuleError.prototype = Object.create(Error.prototype);
33
34UnknownModuleError.prototype.constructor = UnknownModuleError;
35
36module.exports.UnknownModuleError = UnknownModuleError;
37
38function spawnIdeClient(body, options) {
39 var ideClientCommand = options.pscIdeClient || 'purs';
40
41 var ideClientArgs = (options.pscIdeClient ? [] : ['ide', 'client']).concat(dargs(options.pscIdeClientArgs));
42
43 var stderr = [];
44
45 var stdout = [];
46
47 debug('ide client %s %o %O', ideClientCommand, ideClientArgs, body);
48
49 return new Promise(function (resolve, reject) {
50 var ideClient = spawn(ideClientCommand, ideClientArgs);
51
52 ideClient.stderr.on('data', function (data) {
53 stderr.push(data.toString());
54 });
55
56 ideClient.stdout.on('data', function (data) {
57 stdout.push(data.toString());
58 });
59
60 ideClient.on('close', function (code) {
61 if (code !== 0) {
62 var errorMessage = stderr.join('');
63
64 reject(new Error('ide client failed: ' + errorMessage));
65 } else {
66 var result = stdout.join('');
67
68 resolve(result);
69 }
70 });
71
72 ideClient.stdin.resume();
73
74 ideClient.stdin.write(JSON.stringify(body));
75
76 ideClient.stdin.write('\n');
77 });
78}
79
80function formatIdeResult(result, options, index, length) {
81 var numAndErr = '[' + (index + 1) + '/' + length + ' ' + result.errorCode + ']';
82 numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr;
83
84 function makeResult() {
85 return Promise.resolve('\n' + numAndErr + ' ' + result.message);
86 }
87
88 function makeResultSnippet(filename, pos) {
89 var srcPath = path.relative(options.context, filename);
90 var fileAndPos = srcPath + ':' + pos.startLine + ':' + pos.startColumn;
91
92 return fs.readFileAsync(filename, 'utf8').then(function (source) {
93 var lines = source.split('\n').slice(pos.startLine - 1, pos.endLine);
94 var endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine;
95 var up = options.pscIdeColors ? colors.red('^') : '^';
96 var down = options.pscIdeColors ? colors.red('v') : 'v';
97 var trimmed = lines.slice(0);
98
99 if (endsOnNewline) {
100 lines.splice(lines.length - 1, 1);
101 pos.endLine = pos.endLine - 1;
102 pos.endColumn = lines[lines.length - 1].length || 1;
103 }
104
105 // strip newlines at the end
106 if (endsOnNewline) {
107 trimmed = lines.reverse().reduce(function (trimmed, line, i) {
108 if (i === 0 && line === '') trimmed.trimming = true;
109 if (!trimmed.trimming) trimmed.push(line);
110 if (trimmed.trimming && line !== '') {
111 trimmed.trimming = false;
112 trimmed.push(line);
113 }
114 return trimmed;
115 }, []).reverse();
116 pos.endLine = pos.endLine - (lines.length - trimmed.length);
117 pos.endColumn = trimmed[trimmed.length - 1].length || 1;
118 }
119
120 var spaces = ' '.repeat(String(pos.endLine).length);
121 var snippet = trimmed.map(function (line, i) {
122 return ' ' + (pos.startLine + i) + ' ' + line;
123 }).join('\n');
124
125 if (trimmed.length === 1) {
126 snippet += '\n ' + spaces + ' ' + ' '.repeat(pos.startColumn - 1) + up.repeat(pos.endColumn - pos.startColumn + 1);
127 } else {
128 snippet = ' ' + spaces + ' ' + ' '.repeat(pos.startColumn - 1) + down + '\n' + snippet;
129 snippet += '\n ' + spaces + ' ' + ' '.repeat(pos.endColumn - 1) + up;
130 }
131
132 return Promise.resolve('\n' + numAndErr + ' ' + fileAndPos + '\n\n' + snippet + '\n\n' + result.message);
133 }).catch(function (error) {
134 debug('failed to format ide result: %o', error);
135
136 return Promise.resolve('');
137 });
138 }
139
140 return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult();
141}
142
143module.exports.connect = function connect(psModule) {
144 var options = psModule.options;
145
146 var serverCommand = options.pscIdeServer || 'purs';
147
148 var serverArgs = (options.pscIdeServer ? [] : ['ide', 'server']).concat(dargs(Object.assign({
149 outputDirectory: options.output,
150 '_': options.src
151 }, options.pscIdeServerArgs)));
152
153 debug('ide server: %s %o', serverCommand, serverArgs);
154
155 var ideServer = spawn(serverCommand, serverArgs);
156
157 ideServer.stdout.on('data', function (data) {
158 debugVerbose('ide server stdout: %s', data.toString());
159 });
160
161 ideServer.stderr.on('data', function (data) {
162 debugVerbose('ide server stderr: %s', data.toString());
163 });
164
165 ideServer.on('error', function (error) {
166 debugVerbose('ide server error: %o', error);
167 });
168
169 ideServer.on('close', function (code, signal) {
170 debugVerbose('ide server close: %s %s', code, signal);
171 });
172
173 return Promise.resolve(ideServer);
174};
175
176module.exports.load = function load(psModule) {
177 var options = psModule.options;
178
179 var body = { command: 'load' };
180
181 return spawnIdeClient(body, options);
182};
183
184module.exports.loadWithRetry = function loadWithRetry(psModule) {
185 var retries = 9;
186
187 return retryPromise(function (retry, number) {
188 debugVerbose('attempting to load modules (%d out of %d attempts)', number, retries);
189
190 return module.exports.load(psModule).catch(retry);
191 }, {
192 retries: retries,
193 factor: 1,
194 minTimeout: 333,
195 maxTimeout: 333
196 }).then(function () {
197 return psModule;
198 });
199};
200
201module.exports.rebuild = function rebuild(psModule) {
202 var options = psModule.options;
203
204 var body = {
205 command: 'rebuild',
206 params: {
207 file: psModule.srcPath
208 }
209 };
210
211 var parseResponse = function parseResponse(response) {
212 try {
213 var parsed = JSON.parse(response);
214
215 debugVerbose('parsed JSON response: %O', parsed);
216
217 return Promise.resolve(parsed);
218 } catch (error) {
219 return Promise.reject(error);
220 }
221 };
222
223 var formatResponse = function formatResponse(parsed) {
224 var result = Array.isArray(parsed.result) ? parsed.result : [];
225
226 return Promise.map(result, function (item, i) {
227 debugVerbose('formatting result %O', item);
228
229 return formatIdeResult(item, options, i, result.length);
230 }).then(function (formatted) {
231 return {
232 parsed: parsed,
233 formatted: formatted,
234 formattedMessage: formatted.join('\n')
235 };
236 });
237 };
238
239 return spawnIdeClient(body, options).then(parseResponse).then(formatResponse).then(function (_ref) {
240 var parsed = _ref.parsed,
241 formatted = _ref.formatted,
242 formattedMessage = _ref.formattedMessage;
243
244 if (parsed.resultType === 'success') {
245 if (options.warnings && formattedMessage.length) {
246 psModule.emitWarning(formattedMessage);
247 }
248
249 return psModule;
250 } else if ((parsed.result || []).some(function (item) {
251 var isModuleNotFound = item.errorCode === 'ModuleNotFound';
252
253 var isUnknownModule = item.errorCode === 'UnknownModule';
254
255 var isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message);
256
257 return isModuleNotFound || isUnknownModule || isUnknownModuleImport;
258 })) {
259 debug('module %s was not rebuilt because the module is unknown', psModule.name);
260
261 return Promise.reject(new UnknownModuleError());
262 } else {
263 if (formattedMessage.length) {
264 psModule.emitError(formattedMessage);
265 }
266
267 return psModule;
268 }
269 });
270};