]> git.immae.eu Git - github/fretlink/purs-loader.git/blame - src/ide.js
Sourcemaps for ide rebuild (#118)
[github/fretlink/purs-loader.git] / src / ide.js
CommitLineData
531c751f 1'use strict';
2
3const path = require('path');
4
5const Promise = require('bluebird');
6
7const fs = Promise.promisifyAll(require('fs'));
8
9const retryPromise = require('promise-retry');
10
11const spawn = require('cross-spawn');
12
13const colors = require('chalk');
14
7f0547d4 15const debug_ = require('debug');
531c751f 16
7f0547d4 17const debug = debug_('purs-loader');
531c751f 18
7f0547d4 19const debugVerbose = debug_('purs-loader:verbose');
531c751f 20
7f0547d4 21const dargs = require('./dargs');
531c751f 22
7f0547d4 23const compile = require('./compile');
531c751f 24
6ccb09a5 25const PsModuleMap = require('./purs-module-map');
531c751f 26
7f0547d4 27function UnknownModuleError() {
28 this.name = 'UnknownModuleError';
29 this.stack = (new Error()).stack;
30}
531c751f 31
7f0547d4 32UnknownModuleError.prototype = Object.create(Error.prototype);
531c751f 33
7f0547d4 34UnknownModuleError.prototype.constructor = UnknownModuleError;
4305f5b0 35
7f0547d4 36module.exports.UnknownModuleError = UnknownModuleError;
4305f5b0 37
7f0547d4 38function spawnIdeClient(body, options) {
71a96808 39 const ideClientCommand = options.pscIdeClient || 'purs';
4305f5b0 40
71a96808 41 const ideClientArgs = (options.pscIdeClient ? [] : ['ide', 'client']).concat(dargs(options.pscIdeClientArgs));
4305f5b0 42
7f0547d4 43 const stderr = [];
531c751f 44
7f0547d4 45 const stdout = [];
531c751f 46
78e2b0d9 47 debug('ide client %s %o %O', ideClientCommand, ideClientArgs, body);
531c751f 48
7f0547d4 49 return new Promise((resolve, reject) => {
50 const ideClient = spawn(ideClientCommand, ideClientArgs);
531c751f 51
7f0547d4 52 ideClient.stderr.on('data', data => {
53 stderr.push(data.toString());
531c751f 54 })
531c751f 55
56 ideClient.stdout.on('data', data => {
7f0547d4 57 stdout.push(data.toString());
531c751f 58 })
59
60 ideClient.on('close', code => {
61 if (code !== 0) {
7f0547d4 62 const errorMessage = stderr.join('');
531c751f 63
7f0547d4 64 reject(new Error(`ide client failed: ${errorMessage}`));
531c751f 65 }
7f0547d4 66 else {
67 const result = stdout.join('');
531c751f 68
7f0547d4 69 resolve(result);
531c751f 70 }
531c751f 71 })
72
7f0547d4 73 ideClient.stdin.resume();
4305f5b0 74
7f0547d4 75 ideClient.stdin.write(JSON.stringify(body));
531c751f 76
7f0547d4 77 ideClient.stdin.write('\n');
78 });
79}
531c751f 80
81function formatIdeResult(result, options, index, length) {
531c751f 82 let numAndErr = `[${index+1}/${length} ${result.errorCode}]`
83 numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr
84
055d127b 85 function makeResult() {
86 return Promise.resolve(`\n${numAndErr} ${result.message}`)
87 }
88
89 function makeResultSnippet(filename, pos) {
90 const srcPath = path.relative(options.context, filename);
91 const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}`
92
93 return fs.readFileAsync(filename, 'utf8').then(source => {
94 const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine)
95 const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine
96 const up = options.pscIdeColors ? colors.red('^') : '^'
97 const down = options.pscIdeColors ? colors.red('v') : 'v'
98 let trimmed = lines.slice(0)
99
100 if (endsOnNewline) {
101 lines.splice(lines.length - 1, 1)
102 pos.endLine = pos.endLine - 1
103 pos.endColumn = lines[lines.length - 1].length || 1
104 }
531c751f 105
055d127b 106 // strip newlines at the end
107 if (endsOnNewline) {
108 trimmed = lines.reverse().reduce((trimmed, line, i) => {
109 if (i === 0 && line === '') trimmed.trimming = true
110 if (!trimmed.trimming) trimmed.push(line)
111 if (trimmed.trimming && line !== '') {
112 trimmed.trimming = false
113 trimmed.push(line)
114 }
115 return trimmed
116 }, []).reverse()
117 pos.endLine = pos.endLine - (lines.length - trimmed.length)
118 pos.endColumn = trimmed[trimmed.length - 1].length || 1
119 }
531c751f 120
055d127b 121 const spaces = ' '.repeat(String(pos.endLine).length)
122 let snippet = trimmed.map((line, i) => {
123 return ` ${pos.startLine + i} ${line}`
124 }).join('\n')
531c751f 125
055d127b 126 if (trimmed.length === 1) {
127 snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}`
128 } else {
129 snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}`
130 snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}`
131 }
531c751f 132
055d127b 133 return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`)
7f0547d4 134 }).catch(error => {
135 debug('failed to format ide result: %o', error);
136
137 return Promise.resolve('');
138 });
055d127b 139 }
140
141 return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult();
531c751f 142}
7f0547d4 143
144module.exports.connect = function connect(psModule) {
145 const options = psModule.options
146
71a96808 147 const serverCommand = options.pscIdeServer || 'purs';
7f0547d4 148
71a96808 149 const serverArgs = (options.pscIdeServer ? [] : ['ide', 'server']).concat(dargs(Object.assign({
7f0547d4 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',
cd124999 205 params: Object.assign({
7f0547d4 206 file: psModule.srcPath,
cd124999 207 }, options.pscIdeRebuildArgs)
7f0547d4 208 };
209
210 const parseResponse = response => {
211 try {
212 const parsed = JSON.parse(response);
213
78e2b0d9 214 debugVerbose('parsed JSON response: %O', parsed);
7f0547d4 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) => {
78e2b0d9 227 debugVerbose('formatting result %O', item);
7f0547d4 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 })) {
78e2b0d9 257 debug('module %s was not rebuilt because the module is unknown', psModule.name);
7f0547d4 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};