1
1
fs = require ' fs'
2
2
path = require ' path'
3
- spawn = require (' child_process' )
4
- semver = require ' semver'
5
- {BufferedProcess , CompositeDisposable } = require ' atom'
6
3
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'
19
7
20
8
21
9
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})\\ ))?' )
22
16
cargoDependencyDir : " target/debug/deps"
23
- lintProcess : null
24
- cachedAbleToJsonErrors : null
25
-
26
17
27
18
constructor : ->
28
19
@subscriptions = new CompositeDisposable
@@ -64,102 +55,106 @@ class LinterRust
64
55
(specifiedFeatures ) =>
65
56
@specifiedFeatures = specifiedFeatures
66
57
67
-
68
58
destroy : ->
69
59
do @subscriptions .dispose
70
60
71
-
72
61
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 (' ' )} \n See 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 } "
99
121
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
+ []
126
123
127
- parseJSON : (results ) =>
124
+ parseJSON : (output ) =>
128
125
elements = []
126
+ results = output .split ' \n '
129
127
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
157
153
@ buildMessages (elements)
158
154
159
-
160
155
parse : (output ) =>
161
156
elements = []
162
- XRegExp .forEach output, pattern, (match ) ->
157
+ XRegExp .forEach output, @ pattern , (match ) ->
163
158
if match .from_col == match .to_col
164
159
match .to_col = parseInt (match .to_col ) + 1
165
160
range = [
@@ -179,7 +174,6 @@ class LinterRust
179
174
elements .push element
180
175
@ buildMessages elements
181
176
182
-
183
177
buildMessages : (elements ) =>
184
178
messages = []
185
179
lastMessage = null
@@ -216,7 +210,6 @@ class LinterRust
216
210
messages .push lastMessage
217
211
return messages
218
212
219
-
220
213
constructMessage : (type , element ) ->
221
214
message =
222
215
type : type
@@ -234,8 +227,7 @@ class LinterRust
234
227
range : children .range or element .range
235
228
message
236
229
237
-
238
- initCmd : (editingFile ) =>
230
+ initCmd : (editingFile , ableToJSONErrors ) =>
239
231
rustcArgs = switch @rustcBuildTest
240
232
when true then [' --cfg' , ' test' , ' -Z' , ' no-trans' , ' --color' , ' never' ]
241
233
else [' -Z' , ' no-trans' , ' --color' , ' never' ]
@@ -246,28 +238,30 @@ class LinterRust
246
238
when ' clippy' then [' clippy' ]
247
239
else [' build' ]
248
240
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]
260
253
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]
268
262
269
263
compilationFeatures : (cargo ) =>
270
- if @specifiedFeatures
264
+ if @specifiedFeatures . length > 0
271
265
if cargo
272
266
[' --features' , @specifiedFeatures .join (' ' )]
273
267
else
@@ -276,18 +270,19 @@ class LinterRust
276
270
result .push [' --cfg' , " feature=\" #{ f} \" " ]
277
271
result
278
272
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
291
286
292
287
locateCargo : (curDir ) =>
293
288
root_dir = if / ^ win/ .test process .platform then / ^ . :\\ $ / else / ^ \/ $ /
@@ -298,19 +293,24 @@ class LinterRust
298
293
directory = path .resolve path .join (directory, ' ..' )
299
294
return false
300
295
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
315
315
316
316
module .exports = LinterRust
0 commit comments