From 531c751fe5593750a377db38bcfaf9a5383ac661 Mon Sep 17 00:00:00 2001 From: eric thul Date: Sun, 12 Jun 2016 15:17:44 -0400 Subject: Reduce building of PureScript module map Resolves #59 and resolves #60 --- .gitignore | 2 +- package.json | 8 +- src/PsModuleMap.js | 66 +++++++++ src/Psc.js | 92 +++++++++++++ src/PscIde.js | 231 +++++++++++++++++++++++++++++++ src/dargs.js | 16 +++ src/index.js | 394 ++++++----------------------------------------------- 7 files changed, 449 insertions(+), 360 deletions(-) create mode 100644 src/PsModuleMap.js create mode 100644 src/Psc.js create mode 100644 src/PscIde.js create mode 100644 src/dargs.js diff --git a/.gitignore b/.gitignore index 7d50432..4b26e66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ **DS_Store* node_modules/ -index.js +lib/ diff --git a/package.json b/package.json index 5fc5a7f..6c276ce 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,15 @@ "name": "purs-loader", "version": "2.0.0-rc.0", "description": "A webpack loader for PureScript.", - "main": "index.js", + "main": "lib/index.js", "files": [ "LICENSE", "README.md", - "index.js", - "src/index.js" + "src", + "lib" ], "scripts": { - "build": "babel src/index.js -o index.js", + "build": "babel src --out-dir lib", "prepublish": "npm run build", "test": "echo \"Error: no test specified\" && exit 1" }, diff --git a/src/PsModuleMap.js b/src/PsModuleMap.js new file mode 100644 index 0000000..2193f02 --- /dev/null +++ b/src/PsModuleMap.js @@ -0,0 +1,66 @@ +'use strict'; + +const path = require('path'); + +const Promise = require('bluebird'); + +const fs = Promise.promisifyAll(require('fs')); + +const globby = require('globby'); + +const debug = require('debug')('purs-loader') + +const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i + +function match(str) { + const matches = str.match(srcModuleRegex); + return matches && matches[1]; +} +module.exports.match = match; + +function makeMapEntry(filePurs) { + const dirname = path.dirname(filePurs); + + const basename = path.basename(filePurs, '.purs'); + + const fileJs = path.join(dirname, `${basename}.js`); + + const result = Promise.props({ + filePurs: fs.readFileAsync(filePurs, 'utf8'), + fileJs: fs.readFileAsync(fileJs, 'utf8').catch(() => undefined) + }).then(fileMap => { + const sourcePurs = fileMap.filePurs; + + const sourceJs = fileMap.fileJs; + + const moduleName = match(sourcePurs); + + const map = {}; + + map[moduleName] = map[moduleName] || {}; + + map[moduleName].src = path.resolve(filePurs); + + if (sourceJs) { + map[moduleName].ffi = path.resolve(fileJs); + } + + return map; + }); + + return result; +} +module.exports.makeMapEntry = makeMapEntry; + +function makeMap(src) { + debug('loading PureScript source and FFI files from %o', src); + + const globs = [].concat(src); + + return globby(globs).then(paths => + Promise.all(paths.map(makeMapEntry)).then(result => + result.reduce(Object.assign, {}) + ) + ); +} +module.exports.makeMap = makeMap; diff --git a/src/Psc.js b/src/Psc.js new file mode 100644 index 0000000..9269e0f --- /dev/null +++ b/src/Psc.js @@ -0,0 +1,92 @@ +'use strict'; + +const path = require('path'); + +const Promise = require('bluebird') + +const fs = Promise.promisifyAll(require('fs')) + +const spawn = require('cross-spawn') + +const debug = require('debug')('purs-loader'); + +const dargs = require('./dargs'); + +function compile(psModule) { + const options = psModule.options + const cache = psModule.cache + const stderr = [] + + if (cache.compilationStarted) return Promise.resolve(psModule) + + cache.compilationStarted = true + + const args = dargs(Object.assign({ + _: options.src, + output: options.output, + }, options.pscArgs)) + + debug('spawning compiler %s %o', options.psc, args) + + return (new Promise((resolve, reject) => { + console.log('\nCompiling PureScript...') + + const compilation = spawn(options.psc, args) + + compilation.stdout.on('data', data => stderr.push(data.toString())) + compilation.stderr.on('data', data => stderr.push(data.toString())) + + compilation.on('close', code => { + console.log('Finished compiling PureScript.') + cache.compilationFinished = true + if (code !== 0) { + cache.errors = stderr.join('') + reject(true) + } else { + cache.warnings = stderr.join('') + resolve(psModule) + } + }) + })) + .then(compilerOutput => { + if (options.bundle) { + return bundle(options, cache).then(() => psModule) + } + return psModule + }) +} +module.exports.compile = compile; + +function bundle(options, cache) { + if (cache.bundle) return Promise.resolve(cache.bundle) + + const stdout = [] + const stderr = cache.bundle = [] + + const args = dargs(Object.assign({ + _: [path.join(options.output, '*', '*.js')], + output: options.bundleOutput, + namespace: options.bundleNamespace, + }, options.pscBundleArgs)) + + cache.bundleModules.forEach(name => args.push('--module', name)) + + debug('spawning bundler %s %o', options.pscBundle, args.join(' ')) + + return (new Promise((resolve, reject) => { + console.log('Bundling PureScript...') + + const compilation = spawn(options.pscBundle, args) + + compilation.stdout.on('data', data => stdout.push(data.toString())) + compilation.stderr.on('data', data => stderr.push(data.toString())) + compilation.on('close', code => { + if (code !== 0) { + cache.errors = (cache.errors || '') + stderr.join('') + return reject(true) + } + cache.bundle = stderr + resolve(fs.appendFileAsync('output/bundle.js', `module.exports = ${options.bundleNamespace}`)) + }) + })) +} diff --git a/src/PscIde.js b/src/PscIde.js new file mode 100644 index 0000000..d99b639 --- /dev/null +++ b/src/PscIde.js @@ -0,0 +1,231 @@ +'use strict'; + +const path = require('path'); + +const Promise = require('bluebird'); + +const fs = Promise.promisifyAll(require('fs')); + +const retryPromise = require('promise-retry'); + +const spawn = require('cross-spawn'); + +const colors = require('chalk'); + +const debug = require('debug')('purs-loader'); + +const dargs = require('./dargs'); + +const Psc = require('./Psc'); + +const PsModuleMap = require('./PsModuleMap'); + +function connect(psModule) { + const options = psModule.options + const cache = psModule.cache + + if (cache.ideServer) return Promise.resolve(psModule) + + cache.ideServer = true + + const connect_ = () => new Promise((resolve, reject) => { + const args = dargs(options.pscIdeArgs) + + debug('attempting to connect to psc-ide-server', args) + + const ideClient = spawn('psc-ide-client', args) + + ideClient.stderr.on('data', data => { + debug(data.toString()) + cache.ideServer = false + reject(true) + }) + ideClient.stdout.once('data', data => { + debug(data.toString()) + if (data.toString()[0] === '{') { + const res = JSON.parse(data.toString()) + if (res.resultType === 'success') { + cache.ideServer = ideServer + resolve(psModule) + } else { + cache.ideServer = ideServer + reject(true) + } + } else { + cache.ideServer = false + reject(true) + } + }) + ideClient.stdin.resume() + ideClient.stdin.write(JSON.stringify({ command: 'load' })) + ideClient.stdin.write('\n') + }) + + const args = dargs(Object.assign({ + outputDirectory: options.output, + }, options.pscIdeArgs)) + + debug('attempting to start psc-ide-server', args) + + const ideServer = cache.ideServer = spawn('psc-ide-server', []) + ideServer.stderr.on('data', data => { + debug(data.toString()) + }) + + return retryPromise((retry, number) => { + return connect_().catch(error => { + if (!cache.ideServer && number === 9) { + debug(error) + + console.log( + 'failed to connect to or start psc-ide-server, ' + + 'full compilation will occur on rebuild' + ) + + return Promise.resolve(psModule) + } + + return retry(error) + }) + }, { + retries: 9, + factor: 1, + minTimeout: 333, + maxTimeout: 333, + }) +} +module.exports.connect = connect; + +function rebuild(psModule) { + const options = psModule.options + const cache = psModule.cache + + debug('attempting rebuild with psc-ide-client %s', psModule.srcPath) + + const request = (body) => new Promise((resolve, reject) => { + const args = dargs(options.pscIdeArgs) + const ideClient = spawn('psc-ide-client', args) + + var stdout = '' + var stderr = '' + + ideClient.stdout.on('data', data => { + stdout = stdout + data.toString() + }) + + ideClient.stderr.on('data', data => { + stderr = stderr + data.toString() + }) + + ideClient.on('close', code => { + if (code !== 0) { + const error = stderr === '' ? 'Failed to spawn psc-ide-client' : stderr + return reject(new Error(error)) + } + + let res = null + + try { + res = JSON.parse(stdout.toString()) + debug(res) + } catch (err) { + return reject(err) + } + + if (res && !Array.isArray(res.result)) { + return res.resultType === 'success' + ? resolve(psModule) + : reject('psc-ide rebuild failed') + } + + Promise.map(res.result, (item, i) => { + debug(item) + return formatIdeResult(item, options, i, res.result.length) + }) + .then(compileMessages => { + if (res.resultType === 'error') { + if (res.result.some(item => item.errorCode === 'UnknownModule' || item.errorCode === 'UnknownName')) { + debug('unknown module, attempting full recompile') + return Psc.compile(psModule) + .then(() => PsModuleMap.makeMap(options.src).then(map => { + debug('rebuilt module map'); + cache.psModuleMap = map; + })) + .then(() => request({ command: 'load' })) + .then(resolve) + .catch(() => reject('psc-ide rebuild failed')) + } + cache.errors = compileMessages.join('\n') + reject('psc-ide rebuild failed') + } else { + cache.warnings = compileMessages.join('\n') + resolve(psModule) + } + }) + }) + + ideClient.stdin.write(JSON.stringify(body)) + ideClient.stdin.write('\n') + }) + + return request({ + command: 'rebuild', + params: { + file: psModule.srcPath, + } + }) +} +module.exports.rebuild = rebuild; + +function formatIdeResult(result, options, index, length) { + const srcPath = path.relative(options.context, result.filename) + const pos = result.position + const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` + let numAndErr = `[${index+1}/${length} ${result.errorCode}]` + numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr + + return fs.readFileAsync(result.filename, 'utf8').then(source => { + const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) + const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine + const up = options.pscIdeColors ? colors.red('^') : '^' + const down = options.pscIdeColors ? colors.red('v') : 'v' + let trimmed = lines.slice(0) + + if (endsOnNewline) { + lines.splice(lines.length - 1, 1) + pos.endLine = pos.endLine - 1 + pos.endColumn = lines[lines.length - 1].length || 1 + } + + // strip newlines at the end + if (endsOnNewline) { + trimmed = lines.reverse().reduce((trimmed, line, i) => { + if (i === 0 && line === '') trimmed.trimming = true + if (!trimmed.trimming) trimmed.push(line) + if (trimmed.trimming && line !== '') { + trimmed.trimming = false + trimmed.push(line) + } + return trimmed + }, []).reverse() + pos.endLine = pos.endLine - (lines.length - trimmed.length) + pos.endColumn = trimmed[trimmed.length - 1].length || 1 + } + + const spaces = ' '.repeat(String(pos.endLine).length) + let snippet = trimmed.map((line, i) => { + return ` ${pos.startLine + i} ${line}` + }).join('\n') + + if (trimmed.length === 1) { + snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` + } else { + snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` + snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` + } + + return Promise.resolve( + `\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}` + ) + }) +} diff --git a/src/dargs.js b/src/dargs.js new file mode 100644 index 0000000..e5c574c --- /dev/null +++ b/src/dargs.js @@ -0,0 +1,16 @@ +'use strict'; + +function dargs(obj) { + return Object.keys(obj).reduce((args, key) => { + const arg = '--' + key.replace(/[A-Z]/g, '-$&').toLowerCase(); + const val = obj[key] + + if (key === '_') val.forEach(v => args.push(v)) + else if (Array.isArray(val)) val.forEach(v => args.push(arg, v)) + else args.push(arg, obj[key]) + + return args.filter(arg => (typeof arg !== 'boolean')) + }, []) +} + +module.exports = dargs; diff --git a/src/index.js b/src/index.js index cfba1e2..fe1455b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,16 @@ 'use strict' -const colors = require('chalk') const debug = require('debug')('purs-loader') const loaderUtils = require('loader-utils') -const globby = require('globby') const Promise = require('bluebird') const fs = Promise.promisifyAll(require('fs')) -const spawn = require('cross-spawn') const path = require('path') -const retryPromise = require('promise-retry') const jsStringEscape = require('js-string-escape') +const PsModuleMap = require('./PsModuleMap'); +const Psc = require('./Psc'); +const PscIde = require('./PscIde'); +const dargs = require('./dargs'); -const srcModuleRegex = /(?:^|\n)module\s+([\w\.]+)/i const requireRegex = /require\(['"]\.\.\/([\w\.]+)['"]\)/g module.exports = function purescriptLoader(source, map) { @@ -45,7 +44,7 @@ module.exports = function purescriptLoader(source, map) { let cache = config.purescriptLoaderCache = config.purescriptLoaderCache || { rebuild: false, deferred: [], - bundleModules: [], + bundleModules: [] } if (!config.purescriptLoaderInstalled) { @@ -53,10 +52,14 @@ module.exports = function purescriptLoader(source, map) { // invalidate loader cache when bundle is marked as invalid (in watch mode) this._compiler.plugin('invalid', () => { + debug('invalidating loader cache'); + cache = config.purescriptLoaderCache = { rebuild: options.pscIde, deferred: [], - ideServer: cache.ideServer + bundleModules: [], + ideServer: cache.ideServer, + psModuleMap: cache.psModuleMap } }) @@ -74,7 +77,7 @@ module.exports = function purescriptLoader(source, map) { }) } - const psModuleName = match(srcModuleRegex, source) + const psModuleName = PsModuleMap.match(source) const psModule = { name: psModuleName, load: js => callback(null, js), @@ -93,8 +96,8 @@ module.exports = function purescriptLoader(source, map) { } if (cache.rebuild) { - return connectIdeServer(psModule) - .then(rebuild) + return PscIde.connect(psModule) + .then(PscIde.rebuild) .then(toJavaScript) .then(psModule.load) .catch(psModule.reject) @@ -109,7 +112,11 @@ module.exports = function purescriptLoader(source, map) { cache.deferred.push(psModule) if (!cache.compilationStarted) { - return compile(psModule) + return Psc.compile(psModule) + .then(() => PsModuleMap.makeMap(options.src).then(map => { + debug('rebuilt module map'); + cache.psModuleMap = map; + })) .then(() => Promise.map(cache.deferred, psModule => { if (typeof cache.ideServer === 'object') cache.ideServer.kill() return toJavaScript(psModule).then(psModule.load) @@ -121,6 +128,26 @@ module.exports = function purescriptLoader(source, map) { } } +function updatePsModuleMap(psModule) { + const options = psModule.options + const cache = psModule.cache + const filePurs = psModule.srcPath + if (!cache.psModuleMap) { + debug('module mapping does not exist'); + return PsModuleMap.makeMap(options.src).then(map => { + cache.psModuleMap = map; + return cache.psModuleMap; + }); + } + else { + return PsModuleMap.makeMapEntry(filePurs).then(result => { + const map = Object.assign(cache.psModuleMap, result) + cache.psModuleMap = map; + return cache.psModuleMap; + }); + } +} + // The actual loader is executed *after* purescript compilation. function toJavaScript(psModule) { const options = psModule.options @@ -132,7 +159,7 @@ function toJavaScript(psModule) { return Promise.props({ js: fs.readFileAsync(jsPath, 'utf8'), - psModuleMap: psModuleMap(options, cache) + psModuleMap: updatePsModuleMap(psModule) }).then(result => { let js = '' @@ -156,346 +183,3 @@ function toJavaScript(psModule) { return js }) } - -function compile(psModule) { - const options = psModule.options - const cache = psModule.cache - const stderr = [] - - if (cache.compilationStarted) return Promise.resolve(psModule) - - cache.compilationStarted = true - - const args = dargs(Object.assign({ - _: options.src, - output: options.output, - }, options.pscArgs)) - - debug('spawning compiler %s %o', options.psc, args) - - return (new Promise((resolve, reject) => { - console.log('\nCompiling PureScript...') - - const compilation = spawn(options.psc, args) - - compilation.stdout.on('data', data => stderr.push(data.toString())) - compilation.stderr.on('data', data => stderr.push(data.toString())) - - compilation.on('close', code => { - console.log('Finished compiling PureScript.') - cache.compilationFinished = true - if (code !== 0) { - cache.errors = stderr.join('') - reject(true) - } else { - cache.warnings = stderr.join('') - resolve(psModule) - } - }) - })) - .then(compilerOutput => { - if (options.bundle) { - return bundle(options, cache).then(() => psModule) - } - return psModule - }) -} - -function rebuild(psModule) { - const options = psModule.options - const cache = psModule.cache - - debug('attempting rebuild with psc-ide-client %s', psModule.srcPath) - - const request = (body) => new Promise((resolve, reject) => { - const args = dargs(options.pscIdeArgs) - const ideClient = spawn('psc-ide-client', args) - - var stdout = '' - var stderr = '' - - ideClient.stdout.on('data', data => { - stdout = stdout + data.toString() - }) - - ideClient.stderr.on('data', data => { - stderr = stderr + data.toString() - }) - - ideClient.on('close', code => { - if (code !== 0) { - const error = stderr === '' ? 'Failed to spawn psc-ide-client' : stderr - return reject(new Error(error)) - } - - let res = null - - try { - res = JSON.parse(stdout.toString()) - debug(res) - } catch (err) { - return reject(err) - } - - if (res && !Array.isArray(res.result)) { - return res.resultType === 'success' - ? resolve(psModule) - : reject('psc-ide rebuild failed') - } - - Promise.map(res.result, (item, i) => { - debug(item) - return formatIdeResult(item, options, i, res.result.length) - }) - .then(compileMessages => { - if (res.resultType === 'error') { - if (res.result.some(item => item.errorCode === 'UnknownModule')) { - console.log('Unknown module, attempting full recompile') - return compile(psModule) - .then(() => request({ command: 'load' })) - .then(resolve) - .catch(() => reject('psc-ide rebuild failed')) - } - cache.errors = compileMessages.join('\n') - reject('psc-ide rebuild failed') - } else { - cache.warnings = compileMessages.join('\n') - resolve(psModule) - } - }) - }) - - ideClient.stdin.write(JSON.stringify(body)) - ideClient.stdin.write('\n') - }) - - return request({ - command: 'rebuild', - params: { - file: psModule.srcPath, - } - }) -} - -function formatIdeResult(result, options, index, length) { - const srcPath = path.relative(options.context, result.filename) - const pos = result.position - const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` - let numAndErr = `[${index+1}/${length} ${result.errorCode}]` - numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr - - return fs.readFileAsync(result.filename, 'utf8').then(source => { - const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) - const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine - const up = options.pscIdeColors ? colors.red('^') : '^' - const down = options.pscIdeColors ? colors.red('v') : 'v' - let trimmed = lines.slice(0) - - if (endsOnNewline) { - lines.splice(lines.length - 1, 1) - pos.endLine = pos.endLine - 1 - pos.endColumn = lines[lines.length - 1].length || 1 - } - - // strip newlines at the end - if (endsOnNewline) { - trimmed = lines.reverse().reduce((trimmed, line, i) => { - if (i === 0 && line === '') trimmed.trimming = true - if (!trimmed.trimming) trimmed.push(line) - if (trimmed.trimming && line !== '') { - trimmed.trimming = false - trimmed.push(line) - } - return trimmed - }, []).reverse() - pos.endLine = pos.endLine - (lines.length - trimmed.length) - pos.endColumn = trimmed[trimmed.length - 1].length || 1 - } - - const spaces = ' '.repeat(String(pos.endLine).length) - let snippet = trimmed.map((line, i) => { - return ` ${pos.startLine + i} ${line}` - }).join('\n') - - if (trimmed.length === 1) { - snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` - } else { - snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` - snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` - } - - return Promise.resolve( - `\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}` - ) - }) -} - -function bundle(options, cache) { - if (cache.bundle) return Promise.resolve(cache.bundle) - - const stdout = [] - const stderr = cache.bundle = [] - - const args = dargs(Object.assign({ - _: [path.join(options.output, '*', '*.js')], - output: options.bundleOutput, - namespace: options.bundleNamespace, - }, options.pscBundleArgs)) - - cache.bundleModules.forEach(name => args.push('--module', name)) - - debug('spawning bundler %s %o', options.pscBundle, args.join(' ')) - - return (new Promise((resolve, reject) => { - console.log('Bundling PureScript...') - - const compilation = spawn(options.pscBundle, args) - - compilation.stdout.on('data', data => stdout.push(data.toString())) - compilation.stderr.on('data', data => stderr.push(data.toString())) - compilation.on('close', code => { - if (code !== 0) { - cache.errors = (cache.errors || '') + stderr.join('') - return reject(true) - } - cache.bundle = stderr - resolve(fs.appendFileAsync('output/bundle.js', `module.exports = ${options.bundleNamespace}`)) - }) - })) -} - -// map of PS module names to their source path -function psModuleMap(options, cache) { - if (cache.psModuleMap) return Promise.resolve(cache.psModuleMap) - - const globs = [].concat(options.src); - - function pursToJs(file){ - const dirname = path.dirname(file) - const basename = path.basename(file, '.purs') - const fileJS = path.join(dirname, `${basename}.js`) - return fileJS - } - - return globby(globs).then(paths => { - return Promise - .props(paths.reduce((map, file) => { - const fileJS = pursToJs(file) - map[file] = fs.readFileAsync(file, 'utf8') - map[fileJS] = fs.readFileAsync(fileJS, 'utf8').catch(() => undefined) - return map - }, {})) - .then(fileMap => { - cache.psModuleMap = Object.keys(fileMap).reduce((map, file) => { - const ext = path.extname(file) - const isPurs = ext.match(/purs$/i) - if (isPurs) { - const fileJs = pursToJs(file) - const source = fileMap[file] - const ffi = fileMap[fileJs] - const moduleName = match(srcModuleRegex, source) - map[moduleName] = map[moduleName] || {} - map[moduleName].src = path.resolve(file) - if (ffi) { - map[moduleName].ffi = path.resolve(fileJs) - } - } - return map - }, {}) - return cache.psModuleMap - }) - }) -} - -function connectIdeServer(psModule) { - const options = psModule.options - const cache = psModule.cache - - if (cache.ideServer) return Promise.resolve(psModule) - - cache.ideServer = true - - const connect = () => new Promise((resolve, reject) => { - const args = dargs(options.pscIdeArgs) - - debug('attempting to connect to psc-ide-server', args) - - const ideClient = spawn('psc-ide-client', args) - - ideClient.stderr.on('data', data => { - debug(data.toString()) - cache.ideServer = false - reject(true) - }) - ideClient.stdout.once('data', data => { - debug(data.toString()) - if (data.toString()[0] === '{') { - const res = JSON.parse(data.toString()) - if (res.resultType === 'success') { - cache.ideServer = ideServer - resolve(psModule) - } else { - cache.ideServer = ideServer - reject(true) - } - } else { - cache.ideServer = false - reject(true) - } - }) - ideClient.stdin.resume() - ideClient.stdin.write(JSON.stringify({ command: 'load' })) - ideClient.stdin.write('\n') - }) - - const args = dargs(Object.assign({ - outputDirectory: options.output, - }, options.pscIdeArgs)) - - debug('attempting to start psc-ide-server', args) - - const ideServer = cache.ideServer = spawn('psc-ide-server', []) - ideServer.stderr.on('data', data => { - debug(data.toString()) - }) - - return retryPromise((retry, number) => { - return connect().catch(error => { - if (!cache.ideServer && number === 9) { - debug(error) - - console.log( - 'failed to connect to or start psc-ide-server, ' + - 'full compilation will occur on rebuild' - ) - - return Promise.resolve(psModule) - } - - return retry(error) - }) - }, { - retries: 9, - factor: 1, - minTimeout: 333, - maxTimeout: 333, - }) -} - -function match(regex, str) { - const matches = str.match(regex) - return matches && matches[1] -} - -function dargs(obj) { - return Object.keys(obj).reduce((args, key) => { - const arg = '--' + key.replace(/[A-Z]/g, '-$&').toLowerCase(); - const val = obj[key] - - if (key === '_') val.forEach(v => args.push(v)) - else if (Array.isArray(val)) val.forEach(v => args.push(arg, v)) - else args.push(arg, obj[key]) - - return args.filter(arg => (typeof arg !== 'boolean')) - }, []) -} -- cgit v1.2.3 From 055d127bb9ca88c9bfcc7286a33f36f5ff6b4462 Mon Sep 17 00:00:00 2001 From: eric thul Date: Sun, 12 Jun 2016 15:37:34 -0400 Subject: Handle no filename in PscIde result Resolves #58 --- .gitignore | 1 + src/PscIde.js | 92 +++++++++++++++++++++++++++++++---------------------------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index 4b26e66..89f7b48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ **DS_Store* +npm-debug.log node_modules/ lib/ diff --git a/src/PscIde.js b/src/PscIde.js index d99b639..9d6c1ff 100644 --- a/src/PscIde.js +++ b/src/PscIde.js @@ -178,54 +178,60 @@ function rebuild(psModule) { module.exports.rebuild = rebuild; function formatIdeResult(result, options, index, length) { - const srcPath = path.relative(options.context, result.filename) - const pos = result.position - const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` let numAndErr = `[${index+1}/${length} ${result.errorCode}]` numAndErr = options.pscIdeColors ? colors.yellow(numAndErr) : numAndErr - return fs.readFileAsync(result.filename, 'utf8').then(source => { - const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) - const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine - const up = options.pscIdeColors ? colors.red('^') : '^' - const down = options.pscIdeColors ? colors.red('v') : 'v' - let trimmed = lines.slice(0) - - if (endsOnNewline) { - lines.splice(lines.length - 1, 1) - pos.endLine = pos.endLine - 1 - pos.endColumn = lines[lines.length - 1].length || 1 - } + function makeResult() { + return Promise.resolve(`\n${numAndErr} ${result.message}`) + } + + function makeResultSnippet(filename, pos) { + const srcPath = path.relative(options.context, filename); + const fileAndPos = `${srcPath}:${pos.startLine}:${pos.startColumn}` + + return fs.readFileAsync(filename, 'utf8').then(source => { + const lines = source.split('\n').slice(pos.startLine - 1, pos.endLine) + const endsOnNewline = pos.endColumn === 1 && pos.startLine !== pos.endLine + const up = options.pscIdeColors ? colors.red('^') : '^' + const down = options.pscIdeColors ? colors.red('v') : 'v' + let trimmed = lines.slice(0) + + if (endsOnNewline) { + lines.splice(lines.length - 1, 1) + pos.endLine = pos.endLine - 1 + pos.endColumn = lines[lines.length - 1].length || 1 + } - // strip newlines at the end - if (endsOnNewline) { - trimmed = lines.reverse().reduce((trimmed, line, i) => { - if (i === 0 && line === '') trimmed.trimming = true - if (!trimmed.trimming) trimmed.push(line) - if (trimmed.trimming && line !== '') { - trimmed.trimming = false - trimmed.push(line) - } - return trimmed - }, []).reverse() - pos.endLine = pos.endLine - (lines.length - trimmed.length) - pos.endColumn = trimmed[trimmed.length - 1].length || 1 - } + // strip newlines at the end + if (endsOnNewline) { + trimmed = lines.reverse().reduce((trimmed, line, i) => { + if (i === 0 && line === '') trimmed.trimming = true + if (!trimmed.trimming) trimmed.push(line) + if (trimmed.trimming && line !== '') { + trimmed.trimming = false + trimmed.push(line) + } + return trimmed + }, []).reverse() + pos.endLine = pos.endLine - (lines.length - trimmed.length) + pos.endColumn = trimmed[trimmed.length - 1].length || 1 + } - const spaces = ' '.repeat(String(pos.endLine).length) - let snippet = trimmed.map((line, i) => { - return ` ${pos.startLine + i} ${line}` - }).join('\n') + const spaces = ' '.repeat(String(pos.endLine).length) + let snippet = trimmed.map((line, i) => { + return ` ${pos.startLine + i} ${line}` + }).join('\n') - if (trimmed.length === 1) { - snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` - } else { - snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` - snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` - } + if (trimmed.length === 1) { + snippet += `\n ${spaces} ${' '.repeat(pos.startColumn - 1)}${up.repeat(pos.endColumn - pos.startColumn + 1)}` + } else { + snippet = ` ${spaces} ${' '.repeat(pos.startColumn - 1)}${down}\n${snippet}` + snippet += `\n ${spaces} ${' '.repeat(pos.endColumn - 1)}${up}` + } - return Promise.resolve( - `\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}` - ) - }) + return Promise.resolve(`\n${numAndErr} ${fileAndPos}\n\n${snippet}\n\n${result.message}`) + }) + } + + return result.filename && result.position ? makeResultSnippet(result.filename, result.position) : makeResult(); } -- cgit v1.2.3 From 0b853815ef14d35cedebc2c7806fd2f9ff9d5ab5 Mon Sep 17 00:00:00 2001 From: eric thul Date: Sun, 12 Jun 2016 16:04:14 -0400 Subject: Clear warnings and errors after use Resolves #57 --- src/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.js b/src/index.js index fe1455b..c73fdd5 100644 --- a/src/index.js +++ b/src/index.js @@ -67,10 +67,12 @@ module.exports = function purescriptLoader(source, map) { this._compiler.plugin('after-compile', (compilation, callback) => { if (options.warnings && cache.warnings) { compilation.warnings.unshift(`PureScript compilation:\n${cache.warnings}`) + cache.warnings = null; } if (cache.errors) { compilation.errors.unshift(`PureScript compilation:\n${cache.errors}`) + cache.errors = null; } callback() -- cgit v1.2.3