]>
Commit | Line | Data |
---|---|---|
531c751f | 1 | 'use strict'; |
2 | ||
3 | const path = require('path'); | |
4 | ||
5 | const Promise = require('bluebird'); | |
6 | ||
7 | const fs = Promise.promisifyAll(require('fs')); | |
8 | ||
9 | const retryPromise = require('promise-retry'); | |
10 | ||
11 | const spawn = require('cross-spawn'); | |
12 | ||
13 | const colors = require('chalk'); | |
14 | ||
15 | const debug = require('debug')('purs-loader'); | |
16 | ||
17 | const dargs = require('./dargs'); | |
18 | ||
19 | const Psc = require('./Psc'); | |
20 | ||
21 | const PsModuleMap = require('./PsModuleMap'); | |
22 | ||
23 | function connect(psModule) { | |
24 | const options = psModule.options | |
25 | const cache = psModule.cache | |
26 | ||
27 | if (cache.ideServer) return Promise.resolve(psModule) | |
28 | ||
29 | cache.ideServer = true | |
30 | ||
31 | const connect_ = () => new Promise((resolve, reject) => { | |
32 | const args = dargs(options.pscIdeArgs) | |
33 | ||
34 | debug('attempting to connect to psc-ide-server', args) | |
35 | ||
36 | const ideClient = spawn('psc-ide-client', args) | |
37 | ||
38 | ideClient.stderr.on('data', data => { | |
39 | debug(data.toString()) | |
40 | cache.ideServer = false | |
4b99e432 | 41 | reject(new Error('psc-ide-client failed')) |
531c751f | 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 | |
4b99e432 | 52 | reject(new Error('psc-ide-client failed')) |
531c751f | 53 | } |
54 | } else { | |
55 | cache.ideServer = false | |
4b99e432 | 56 | reject(new Error('psc-ide-client failed')) |
531c751f | 57 | } |
58 | }) | |
59 | ideClient.stdin.resume() | |
60 | ideClient.stdin.write(JSON.stringify({ command: 'load' })) | |
61 | ideClient.stdin.write('\n') | |
62 | }) | |
63 | ||
64 | const args = dargs(Object.assign({ | |
65 | outputDirectory: options.output, | |
66 | }, options.pscIdeArgs)) | |
67 | ||
68 | debug('attempting to start psc-ide-server', args) | |
69 | ||
70 | const ideServer = cache.ideServer = spawn('psc-ide-server', []) | |
71 | ideServer.stderr.on('data', data => { | |
72 | debug(data.toString()) | |
73 | }) | |
74 | ||
75 | return retryPromise((retry, number) => { | |
76 | return connect_().catch(error => { | |
77 | if (!cache.ideServer && number === 9) { | |
78 | debug(error) | |
79 | ||
df05d7e1 | 80 | console.warn('Failed to connect to or start psc-ide-server. A full compilation will occur on rebuild'); |
531c751f | 81 | |
82 | return Promise.resolve(psModule) | |
83 | } | |
84 | ||
85 | return retry(error) | |
86 | }) | |
87 | }, { | |
88 | retries: 9, | |
89 | factor: 1, | |
90 | minTimeout: 333, | |
91 | maxTimeout: 333, | |
92 | }) | |
93 | } | |
94 | module.exports.connect = connect; | |
95 | ||
96 | function rebuild(psModule) { | |
97 | const options = psModule.options | |
98 | const cache = psModule.cache | |
99 | ||
100 | debug('attempting rebuild with psc-ide-client %s', psModule.srcPath) | |
101 | ||
102 | const request = (body) => new Promise((resolve, reject) => { | |
103 | const args = dargs(options.pscIdeArgs) | |
104 | const ideClient = spawn('psc-ide-client', args) | |
105 | ||
106 | var stdout = '' | |
107 | var stderr = '' | |
108 | ||
109 | ideClient.stdout.on('data', data => { | |
110 | stdout = stdout + data.toString() | |
111 | }) | |
112 | ||
113 | ideClient.stderr.on('data', data => { | |
114 | stderr = stderr + data.toString() | |
115 | }) | |
116 | ||
117 | ideClient.on('close', code => { | |
118 | if (code !== 0) { | |
119 | const error = stderr === '' ? 'Failed to spawn psc-ide-client' : stderr | |
120 | return reject(new Error(error)) | |
121 | } | |
122 | ||
123 | let res = null | |
124 | ||
125 | try { | |
126 | res = JSON.parse(stdout.toString()) | |
127 | debug(res) | |
128 | } catch (err) { | |
129 | return reject(err) | |
130 | } | |
131 | ||
132 | if (res && !Array.isArray(res.result)) { | |
df05d7e1 | 133 | return resolve(psModule); |
531c751f | 134 | } |
135 | ||
136 | Promise.map(res.result, (item, i) => { | |
137 | debug(item) | |
138 | return formatIdeResult(item, options, i, res.result.length) | |
139 | }) | |
140 | .then(compileMessages => { | |
141 | if (res.resultType === 'error') { | |
28124e28 | 142 | if (res.result.some(item => { |
143 | const isUnknownModule = item.errorCode === 'UnknownModule'; | |
144 | ||
145 | const isUnknownModuleImport = item.errorCode === 'UnknownName' && /Unknown module/.test(item.message); | |
146 | ||
147 | return isUnknownModule || isUnknownModuleImport; | |
148 | })) { | |
531c751f | 149 | debug('unknown module, attempting full recompile') |
150 | return Psc.compile(psModule) | |
151 | .then(() => PsModuleMap.makeMap(options.src).then(map => { | |
152 | debug('rebuilt module map'); | |
153 | cache.psModuleMap = map; | |
154 | })) | |
155 | .then(() => request({ command: 'load' })) | |
156 | .then(resolve) | |
df05d7e1 | 157 | .catch(() => resolve(psModule)) |
531c751f | 158 | } |
159 | cache.errors = compileMessages.join('\n') | |
df05d7e1 | 160 | resolve(psModule); |
531c751f | 161 | } else { |
162 | cache.warnings = compileMessages.join('\n') | |
163 | resolve(psModule) | |
164 | } | |
165 | }) | |
166 | }) | |
167 | ||
168 | ideClient.stdin.write(JSON.stringify(body)) | |
169 | ideClient.stdin.write('\n') | |
170 | }) | |
171 | ||
172 | return request({ | |
173 | command: 'rebuild', | |
174 | params: { | |
175 | file: psModule.srcPath, | |
176 | } | |
177 | }) | |
178 | } | |
179 | module.exports.rebuild = rebuild; | |
180 | ||
181 | function formatIdeResult(result, options, index, length) { | |
531c751f | 182 | let numAndErr = `[${index+1}/${length} ${result.errorCode}]` |
183 | numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr | |
184 | ||
055d127b | 185 | function makeResult() { |
186 | return Promise.resolve(`\n${numAndErr} ${result.message}`) | |
187 | } | |
188 | ||
189 | function makeResultSnippet(filename, pos) { | |
190 | const srcPath = path.relative(options.context, filename); | |
191 | const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` | |
192 | ||
193 | return fs.readFileAsync(filename, 'utf8').then(source => { | |
194 | const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) | |
195 | const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine | |
196 | const up = options.pscIdeColors ? colors.red('^') : '^' | |
197 | const down = options.pscIdeColors ? colors.red('v') : 'v' | |
198 | let trimmed = lines.slice(0) | |
199 | ||
200 | if (endsOnNewline) { | |
201 | lines.splice(lines.length - 1, 1) | |
202 | pos.endLine = pos.endLine - 1 | |
203 | pos.endColumn = lines[lines.length - 1].length || 1 | |
204 | } | |
531c751f | 205 | |
055d127b | 206 | // strip newlines at the end |
207 | if (endsOnNewline) { | |
208 | trimmed = lines.reverse().reduce((trimmed, line, i) => { | |
209 | if (i === 0 && line === '') trimmed.trimming = true | |
210 | if (!trimmed.trimming) trimmed.push(line) | |
211 | if (trimmed.trimming && line !== '') { | |
212 | trimmed.trimming = false | |
213 | trimmed.push(line) | |
214 | } | |
215 | return trimmed | |
216 | }, []).reverse() | |
217 | pos.endLine = pos.endLine - (lines.length - trimmed.length) | |
218 | pos.endColumn = trimmed[trimmed.length - 1].length || 1 | |
219 | } | |
531c751f | 220 | |
055d127b | 221 | const spaces = ' '.repeat(String(pos.endLine).length) |
222 | let snippet = trimmed.map((line, i) => { | |
223 | return ` ${pos.startLine + i} ${line}` | |
224 | }).join('\n') | |
531c751f | 225 | |
055d127b | 226 | if (trimmed.length === 1) { |
227 | snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` | |
228 | } else { | |
229 | snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` | |
230 | snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` | |
231 | } | |
531c751f | 232 | |
055d127b | 233 | return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) |
234 | }) | |
235 | } | |
236 | ||
237 | return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); | |
531c751f | 238 | } |