Skip to content
This repository was archived by the owner on Aug 7, 2023. It is now read-only.

Commit 02f72a4

Browse files
White-Oakgmist
authored andcommitted
Use asynchronous execs instead of synchronous (#81)
* Refactored code to use sb-exec, fix #80 (@White-Oak)
1 parent 48d7089 commit 02f72a4

File tree

2 files changed

+152
-151
lines changed

2 files changed

+152
-151
lines changed

lib/linter-rust.coffee

Lines changed: 151 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,19 @@
11
fs = require 'fs'
22
path = require 'path'
3-
spawn = require ('child_process')
4-
semver = require 'semver'
5-
{BufferedProcess, CompositeDisposable} = require 'atom'
63
XRegExp = require 'xregexp'
7-
8-
9-
pattern = XRegExp(
10-
'(?<file>[^\n\r]+):(?<from_line>\\d+):(?<from_col>\\d+):\\s*\
11-
(?<to_line>\\d+):(?<to_col>\\d+)\\s+\
12-
((?<error>error|fatal error)|(?<warning>warning)|(?<info>note|help)):\\s+\
13-
(?<message>.+?)[\n\r]+($|(?=[^\n\r]+:\\d+))', 's'
14-
)
15-
patternRustcVersion = XRegExp(
16-
'rustc (?<version>1.\\d+.\\d+)(?:(?:-(?<nightly>nightly)|(?:[^\\s]+))? \
17-
\\((?:[^\\s]+) (?<date>\\d{4}-\\d{2}-\\d{2})\\))?'
18-
)
4+
semver = require 'semver'
5+
sb_exec = require 'sb-exec'
6+
{CompositeDisposable} = require 'atom'
197

208

219
class LinterRust
10+
pattern: XRegExp('(?<file>[^\n\r]+):(?<from_line>\\d+):(?<from_col>\\d+):\\s*\
11+
(?<to_line>\\d+):(?<to_col>\\d+)\\s+\
12+
((?<error>error|fatal error)|(?<warning>warning)|(?<info>note|help)):\\s+\
13+
(?<message>.+?)[\n\r]+($|(?=[^\n\r]+:\\d+))', 's')
14+
patternRustcVersion: XRegExp('rustc (?<version>1.\\d+.\\d+)(?:(?:-(?<nightly>nightly)|(?:[^\\s]+))? \
15+
\\((?:[^\\s]+) (?<date>\\d{4}-\\d{2}-\\d{2})\\))?')
2216
cargoDependencyDir: "target/debug/deps"
23-
lintProcess: null
24-
cachedAbleToJsonErrors: null
25-
2617

2718
constructor: ->
2819
@subscriptions = new CompositeDisposable
@@ -64,102 +55,106 @@ class LinterRust
6455
(specifiedFeatures) =>
6556
@specifiedFeatures = specifiedFeatures
6657

67-
6858
destroy: ->
6959
do @subscriptions.dispose
7060

71-
7261
lint: (textEditor) =>
73-
return new Promise (resolve, reject) =>
74-
results = []
75-
file = @initCmd do textEditor.getPath
76-
curDir = path.dirname file
77-
PATH = path.dirname @cmd[0]
78-
options =
79-
env: JSON.parse JSON.stringify process.env
80-
options.env.PATH = PATH + path.delimiter + options.env.PATH
81-
options.cwd = curDir
82-
command = @cmd[0]
83-
args = @cmd.slice 1
84-
@cachedAbleToJsonErrors = null
85-
@cachedAbleToJsonErrors = do @ableToJSONErrors
86-
87-
stdout = (data) ->
88-
console.log data if do atom.inDevMode
89-
stderr = (err) ->
90-
if err.indexOf('does not have these features') >= 0
91-
atom.notifications.addError "Invalid specified features",
92-
detail: "#{err}"
93-
dismissable: true
94-
else
95-
if do atom.inDevMode
96-
atom.notifications.addWarning "Output from stderr while linting",
97-
detail: "#{err}"
98-
description: "This is shown because Atom is running in dev-mode and probably not an actual error"
62+
curDir = path.dirname textEditor.getPath()
63+
@ableToJSONErrors(curDir).then (ableToJSONErrors) =>
64+
@initCmd(textEditor.getPath(), ableToJSONErrors).then (result) =>
65+
[file, cmd] = result
66+
env = JSON.parse JSON.stringify process.env
67+
cwd = curDir
68+
command = cmd[0]
69+
args = cmd.slice 1
70+
71+
if ableToJSONErrors
72+
additional = if env.RUSTFLAGS? then ' ' + env.RUSTFLAGS else ''
73+
env.RUSTFLAGS = '--error-format=json' + additional
74+
75+
sb_exec.exec(command, args, {env: env, cwd: cwd, stream: 'both'})
76+
.then (result) =>
77+
{stdout, stderr, exitCode} = result
78+
# first, check if an output says specified features are invalid
79+
if stderr.indexOf('does not have these features') >= 0
80+
atom.notifications.addError "Invalid specified features",
81+
detail: "#{stderr}"
82+
dismissable: true
83+
[]
84+
# then, if exit code looks okay, process an output
85+
else if exitCode is 101 or exitCode is 0
86+
# in dev mode show message boxes with output
87+
showDevModeWarning = (stream, message) ->
88+
atom.notifications.addWarning "Output from #{stream} while linting",
89+
detail: "#{message}"
90+
description: "This is shown because Atom is running in dev-mode and probably not an actual error"
91+
dismissable: true
92+
if do atom.inDevMode
93+
showDevModeWarning('stderr', stderr) if stderr
94+
showDevModeWarning('stdout', stdout) if stdout
95+
96+
# call a needed parser
97+
messages = unless ableToJSONErrors
98+
@parse stderr
99+
else
100+
@parseJSON stderr
101+
102+
# correct file paths
103+
messages.forEach (message) ->
104+
if !(path.isAbsolute message.filePath)
105+
message.filePath = path.join curDir, message.filePath
106+
messages
107+
else
108+
# whoops, we're in trouble -- let's output as much as we can
109+
atom.notifications.addError "Failed to run #{command} with exit code #{exitCode}",
110+
detail: "with args:\n #{args.join(' ')}\nSee console for more information"
111+
dismissable: true
112+
console.log "stdout:"
113+
console.log stdout
114+
console.log "stderr:"
115+
console.log stderr
116+
[]
117+
.catch (error) ->
118+
console.log error
119+
atom.notifications.addError "Failed to run #{command}",
120+
detail: "#{error.message}"
99121
dismissable: true
100-
results.push err
101-
102-
exit = (code) =>
103-
if code is 101 or code is 0
104-
unless do @ableToJSONErrors
105-
messages = @parse results.join('')
106-
else
107-
messages = @parseJSON results
108-
messages.forEach (message) ->
109-
if !(path.isAbsolute message.filePath)
110-
message.filePath = path.join curDir, message.filePath
111-
resolve messages
112-
else
113-
resolve []
114-
115-
if do @ableToJSONErrors
116-
additional = if options.env.RUSTFLAGS? then ' ' + options.env.RUSTFLAGS else ''
117-
options.env.RUSTFLAGS = '--error-format=json' + additional
118-
@lintProcess = new BufferedProcess({command, args, options, stdout, stderr, exit})
119-
@lintProcess.onWillThrowError ({error, handle}) ->
120-
atom.notifications.addError "Failed to run #{command}",
121-
detail: "#{error.message}"
122-
dismissable: true
123-
handle()
124-
resolve []
125-
122+
[]
126123

127-
parseJSON: (results) =>
124+
parseJSON: (output) =>
128125
elements = []
126+
results = output.split '\n'
129127
for result in results
130-
subresults = result.split '\n'
131-
for result in subresults
132-
if result.startsWith '{'
133-
input = JSON.parse result
134-
continue unless input.spans
135-
primary_span = input.spans.find (span) -> span.is_primary
136-
continue unless primary_span
137-
range = [
138-
[primary_span.line_start - 1, primary_span.column_start - 1],
139-
[primary_span.line_end - 1, primary_span.column_end - 1]
140-
]
141-
input.level = 'error' if input == 'fatal error'
142-
element =
143-
type: input.level
144-
message: input.message
145-
file: primary_span.file_name
146-
range: range
147-
children: input.children
148-
for span in input.spans
149-
unless span.is_primary
150-
element.children.push
151-
message: span.label
152-
range: [
153-
[span.line_start - 1, span.column_start - 1],
154-
[span.line_end - 1, span.column_end - 1]
155-
]
156-
elements.push element
128+
if result.startsWith '{'
129+
input = JSON.parse result.trim()
130+
continue unless input.spans
131+
primary_span = input.spans.find (span) -> span.is_primary
132+
continue unless primary_span
133+
range = [
134+
[primary_span.line_start - 1, primary_span.column_start - 1],
135+
[primary_span.line_end - 1, primary_span.column_end - 1]
136+
]
137+
input.level = 'error' if input == 'fatal error'
138+
element =
139+
type: input.level
140+
message: input.message
141+
file: primary_span.file_name
142+
range: range
143+
children: input.children
144+
for span in input.spans
145+
unless span.is_primary
146+
element.children.push
147+
message: span.label
148+
range: [
149+
[span.line_start - 1, span.column_start - 1],
150+
[span.line_end - 1, span.column_end - 1]
151+
]
152+
elements.push element
157153
@buildMessages(elements)
158154

159-
160155
parse: (output) =>
161156
elements = []
162-
XRegExp.forEach output, pattern, (match) ->
157+
XRegExp.forEach output, @pattern, (match) ->
163158
if match.from_col == match.to_col
164159
match.to_col = parseInt(match.to_col) + 1
165160
range = [
@@ -179,7 +174,6 @@ class LinterRust
179174
elements.push element
180175
@buildMessages elements
181176

182-
183177
buildMessages: (elements) =>
184178
messages = []
185179
lastMessage = null
@@ -216,7 +210,6 @@ class LinterRust
216210
messages.push lastMessage
217211
return messages
218212

219-
220213
constructMessage: (type, element) ->
221214
message =
222215
type: type
@@ -234,8 +227,7 @@ class LinterRust
234227
range: children.range or element.range
235228
message
236229

237-
238-
initCmd: (editingFile) =>
230+
initCmd: (editingFile, ableToJSONErrors) =>
239231
rustcArgs = switch @rustcBuildTest
240232
when true then ['--cfg', 'test', '-Z', 'no-trans', '--color', 'never']
241233
else ['-Z', 'no-trans', '--color', 'never']
@@ -246,28 +238,30 @@ class LinterRust
246238
when 'clippy' then ['clippy']
247239
else ['build']
248240

249-
cargoManifestPath = @locateCargo path.dirname editingFile
250-
if not @useCargo or not cargoManifestPath
251-
@cmd = [@rustcPath]
252-
.concat rustcArgs
253-
if cargoManifestPath
254-
@cmd.push '-L'
255-
@cmd.push path.join path.dirname(cargoManifestPath), @cargoDependencyDir
256-
@cmd = @cmd.concat @compilationFeatures(false)
257-
@cmd = @cmd.concat [editingFile]
258-
@cmd = @cmd.concat ['--error-format=json'] if do @ableToJSONErrors
259-
return editingFile
241+
if not @useCargo or not @cargoManifestFilename
242+
Promise.resolve().then () =>
243+
cmd = [@rustcPath]
244+
.concat rustcArgs
245+
if @cargoManifestFilename
246+
cmd.push '-L'
247+
cmd.push path.join path.dirname(@cargoManifestFilename), @cargoDependencyDir
248+
compilationFeatures = @compilationFeatures(false)
249+
cmd = cmd.concat compilationFeatures if compilationFeatures
250+
cmd = cmd.concat [editingFile]
251+
cmd = cmd.concat ['--error-format=json'] if ableToJSONErrors
252+
[editingFile, cmd]
260253
else
261-
@cmd = @buildCargoPath @cargoPath
262-
.concat cargoArgs
263-
.concat ['-j', @jobsNumber]
264-
@cmd = @cmd.concat @compilationFeatures(true)
265-
@cmd = @cmd.concat ['--manifest-path', cargoManifestPath]
266-
return cargoManifestPath
267-
254+
@buildCargoPath(@cargoPath).then (cmd) =>
255+
compilationFeatures = @compilationFeatures(true)
256+
cmd = cmd
257+
.concat cargoArgs
258+
.concat ['-j', @jobsNumber]
259+
cmd = cmd.concat compilationFeatures if compilationFeatures
260+
cmd = cmd.concat ['--manifest-path', @cargoManifestFilename]
261+
[@cargoManifestFilename, cmd]
268262

269263
compilationFeatures: (cargo) =>
270-
if @specifiedFeatures
264+
if @specifiedFeatures.length > 0
271265
if cargo
272266
['--features', @specifiedFeatures.join(' ')]
273267
else
@@ -276,18 +270,19 @@ class LinterRust
276270
result.push ['--cfg', "feature=\"#{f}\""]
277271
result
278272

279-
280-
ableToJSONErrors: () =>
281-
return @cachedAbleToJsonErrors if @cachedAbleToJsonErrors?
282-
result = spawn.execSync @rustcPath + ' --version', {stdio: 'pipe' }
283-
match = XRegExp.exec result, patternRustcVersion
284-
if match and match.nightly and match.date > '2016-08-08'
285-
true
286-
else if match and not match.nightly and semver.gte(match.version, '1.12.0')
287-
true
288-
else
289-
false
290-
273+
ableToJSONErrors: (curDir) =>
274+
# current dir is set to handle overrides
275+
276+
sb_exec.exec(@rustcPath, ['--version'], {stream: 'stdout', cwd: curDir, stdio: 'pipe'}).then (stdout) =>
277+
console.log stdout
278+
try
279+
match = XRegExp.exec(stdout, @patternRustcVersion)
280+
if match and match.nightly and match.date > '2016-08-08'
281+
true
282+
else if match and not match.nightly and semver.gte(match.version, '1.12.0')
283+
true
284+
else
285+
false
291286

292287
locateCargo: (curDir) =>
293288
root_dir = if /^win/.test process.platform then /^.:\\$/ else /^\/$/
@@ -298,19 +293,24 @@ class LinterRust
298293
directory = path.resolve path.join(directory, '..')
299294
return false
300295

301-
302-
buildCargoPath: (cargoPath) =>
303-
if @cargoCommand == 'clippy' and @usingMultirustForClippy()
304-
return ['multirust','run', 'nightly', 'cargo']
305-
else
306-
return [cargoPath]
307-
308-
309-
usingMultirustForClippy: () =>
310-
try
311-
result = spawn.execSync 'multirust --version'
312-
true
313-
catch
314-
false
296+
buildCargoPath: (cargoPath) =>
297+
@usingMultitoolForClippy().then (canUseMultirust) =>
298+
if @cargoCommand == 'clippy' and canUseMultirust.result
299+
[canUseMultirust.tool, 'run', 'nightly', 'cargo']
300+
else
301+
[cargoPath]
302+
303+
usingMultitoolForClippy: () =>
304+
# Try to use rustup
305+
sb_exec.exec 'rustup', ['--version'], {ignoreExitCode: true}
306+
.then ->
307+
result: true, tool: 'rustup'
308+
.catch ->
309+
# Try to use odler multirust at least
310+
sb_exec.exec 'multirust', ['--version'], {ignoreExitCode: true}
311+
.then ->
312+
result: true, tool: 'multirust'
313+
.catch ->
314+
result: false
315315

316316
module.exports = LinterRust

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"dependencies": {
1919
"atom-package-deps": "^4.3.0",
2020
"semver": "^5.3.0",
21+
"sb-exec": "^3.1.0",
2122
"xregexp": "~3.1.0"
2223
},
2324
"package-deps": [

0 commit comments

Comments
 (0)