Severely improved support for rustfmt 0.5

rustfmt will now be run within the actual working directory instead of
a temporary one. This will allow rustfmt to correctly look up
referenced modules.
This commit is contained in:
Leonard Hecker 2016-06-22 14:30:25 +02:00
parent 852c356551
commit 458fdb62e2
2 changed files with 92 additions and 67 deletions

View File

@ -113,7 +113,7 @@ module.exports = class Beautifier
_envCacheDate: null _envCacheDate: null
_envCacheExpiry: 10000 # 10 seconds _envCacheExpiry: 10000 # 10 seconds
getShellEnvironment: -> getShellEnvironment: ->
return new @Promise((resolve, reject) => return new Promise((resolve, reject) =>
# Check Cache # Check Cache
if @_envCache? and @_envCacheDate? if @_envCache? and @_envCacheDate?
# Check if Cache is old # Check if Cache is old
@ -248,71 +248,78 @@ module.exports = class Beautifier
### ###
Run command-line interface command Run command-line interface command
### ###
run: (executable, args, {ignoreReturnCode, help} = {}) -> run: (executable, args, {cwd, ignoreReturnCode, help, onStdin} = {}) ->
# Flatten args first # Flatten args first
args = _.flatten(args) args = _.flatten(args)
# Resolve executable and all args # Resolve executable and all args
Promise.all([executable, Promise.all(args)]) Promise.all([executable, Promise.all(args)])
.then(([exeName, args]) => .then(([exeName, args]) =>
@debug('exeName, args:', exeName, args) @debug('exeName, args:', exeName, args)
return new Promise((resolve, reject) =>
# Remove undefined/null values # Remove undefined/null values
args = _.without(args, undefined) args = _.without(args, undefined)
args = _.without(args, null) args = _.without(args, null)
# Get PATH and other environment variables # Get PATH and other environment variables
Promise.all([@getShellEnvironment(), @which(exeName)]) Promise.all([exeName, @getShellEnvironment(), @which(exeName)])
.then(([env, exePath]) => )
@debug('exePath, env:', exePath, env) .then(([exeName, env, exePath]) =>
exe = exePath ? exeName @debug('exePath, env:', exePath, env)
# Spawn command
options = { exe = exePath ? exeName
env: env options = {
} cwd: cwd
cmd = @spawn(exe, args, options) env: env
.then(({returnCode, stdout, stderr}) => }
@verbose('spawn result', returnCode, stdout, stderr)
# If return code is not 0 then error occured @spawn(exe, args, options, onStdin)
if not ignoreReturnCode and returnCode isnt 0 .then(({returnCode, stdout, stderr}) =>
err = new Error(stderr) @verbose('spawn result', returnCode, stdout, stderr)
windowsProgramNotFoundMsg = "is not recognized as an \
internal or external command" # operable program or batch file # If return code is not 0 then error occured
@verbose(stderr, windowsProgramNotFoundMsg) if not ignoreReturnCode and returnCode isnt 0
if @isWindows and returnCode is 1 and \ # operable program or batch file
stderr.indexOf(windowsProgramNotFoundMsg) isnt -1 windowsProgramNotFoundMsg = "is not recognized as an internal or external command"
err = @commandNotFoundError(exeName, help)
reject(err) @verbose(stderr, windowsProgramNotFoundMsg)
else
resolve(stdout) if @isWindows and returnCode is 1 and stderr.indexOf(windowsProgramNotFoundMsg) isnt -1
) throw @commandNotFoundError(exeName, help)
.catch((err) => else
@debug('error', err) throw new Error(stderr)
# Check if error is ENOENT else
# (command could not be found) stdout
if err.code is 'ENOENT' or err.errno is 'ENOENT' )
reject(@commandNotFoundError(exeName, help)) .catch((err) =>
else @debug('error', err)
# continue as normal error
reject(err) # Check if error is ENOENT (command could not be found)
) if err.code is 'ENOENT' or err.errno is 'ENOENT'
) throw @commandNotFoundError(exeName, help)
else
# continue as normal error
throw err
)
) )
)
### ###
Spawn Spawn
### ###
spawn: (exe, args, options) -> spawn: (exe, args, options, onStdin) ->
return new Promise((resolve, reject) => return new Promise((resolve, reject) =>
@debug('spawn', exe, args) @debug('spawn', exe, args)
cmd = spawn(exe, args, options) cmd = spawn(exe, args, options)
# add a 'data' event listener for the spawn instance
stdout = "" stdout = ""
stderr = "" stderr = ""
cmd.stdout.on('data', (data) -> stdout += data )
cmd.stderr.on('data', (data) -> stderr += data ) cmd.stdout.on('data', (data) ->
# when the spawn child process exits, stdout += data
# check if there were any errors and )
# close the writeable stream cmd.stderr.on('data', (data) ->
stderr += data
)
cmd.on('close', (returnCode) => cmd.on('close', (returnCode) =>
@debug('spawn done', returnCode, stderr, stdout) @debug('spawn done', returnCode, stderr, stdout)
resolve({returnCode, stdout, stderr}) resolve({returnCode, stdout, stderr})
@ -321,6 +328,8 @@ module.exports = class Beautifier
@debug('error', err) @debug('error', err)
reject(err) reject(err)
) )
onStdin cmd.stdin if onStdin
) )
### ###

View File

@ -4,9 +4,11 @@ Requires https://github.com/nrc/rustfmt
"use strict" "use strict"
Beautifier = require('./beautifier') Beautifier = require('./beautifier')
path = require('path')
versionCheckState = false
module.exports = class Rustfmt extends Beautifier module.exports = class Rustfmt extends Beautifier
name: "rustfmt" name: "rustfmt"
options: { options: {
@ -14,24 +16,38 @@ module.exports = class Rustfmt extends Beautifier
} }
beautify: (text, language, options) -> beautify: (text, language, options) ->
# get file path which is the search path for rustfmt.toml as
# the beautifier runs rustfmt in a tmp directory.
# This will pick up any rustfmt.toml defined in the crate root
editor = atom.workspace.getActivePaneItem() editor = atom.workspace.getActivePaneItem()
file = editor?.buffer.file buffer = editor.getBuffer?()
filePath = file?.path filePath = buffer.getPath?()
cwd = if filePath then path.dirname filePath else undefined
program = options.rustfmt_path or "rustfmt" program = options.rustfmt_path or "rustfmt"
@run(program, [ help = {
tmpFile = @tempFile("tmp", text) link: "https://github.com/nrc/rustfmt"
["--write-mode", "overwrite"] program: "rustfmt"
["--config-path", filePath] pathOption: "Rust - Rustfmt Path"
], help: { }
link: "https://github.com/nrc/rustfmt"
program: "rustfmt" # 0.5.0 is a relatively new version at the point of writing,
pathOption: "Rust - Rustfmt Path" # but is essential for this to work with stdin.
# => Check for it specifically.
p = if versionCheckState == program
@Promise.resolve()
else
@run(program, ["--version"], help: help)
.then((stdout) ->
if /^0\.(?:[0-4]\.[0-9])/.test(stdout.trim())
versionCheckState = false
throw new Error("rustfmt version 0.5.0 or newer required")
else
versionCheckState = program
undefined
)
p.then(=>
@run(program, [], {
cwd: cwd
help: help
onStdin: (stdin) ->
stdin.end text
}) })
.then(=> )
@readFile(tmpFile)
)