@@ -4,13 +4,9 @@ XRegExp = require 'xregexp'
4
4
semver = require ' semver'
5
5
sb_exec = require ' sb-exec'
6
6
{CompositeDisposable } = require ' atom'
7
-
7
+ errorModes = require ' ./mode '
8
8
9
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
10
patternRustcVersion : XRegExp (' rustc (?<version>1.\\ d+.\\ d+)(?:(?:-(?<nightly>nightly)|(?:[^\\ s]+))? \
15
11
\\ ((?:[^\\ s]+) (?<date>\\ d{4}-\\ d{2}-\\ d{2})\\ ))?' )
16
12
cargoDependencyDir : " target/debug/deps"
@@ -59,209 +55,80 @@ class LinterRust
59
55
do @subscriptions .dispose
60
56
61
57
lint : (textEditor ) =>
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
- curDir = path .dirname file
68
- cwd = curDir
69
- command = cmd[0 ]
70
- args = cmd .slice 1
71
- env .PATH = path .dirname (cmd[0 ]) + path .delimiter + env .PATH
72
-
73
- if ableToJSONErrors
74
- if ! env .RUSTFLAGS ? or ! (env .RUSTFLAGS .indexOf (' --error-format=json' ) >= 0 )
75
- additional = if env .RUSTFLAGS ? then ' ' + env .RUSTFLAGS else ' '
76
- env .RUSTFLAGS = ' --error-format=json' + additional
77
- sb_exec .exec (command, args, {env : env, cwd : cwd, stream : ' both' })
78
- .then (result) =>
79
- {stdout , stderr , exitCode } = result
80
- # first, check if an output says specified features are invalid
81
- if stderr .indexOf (' does not have these features' ) >= 0
82
- atom .notifications .addError " Invalid specified features" ,
83
- detail : " #{ stderr} "
84
- dismissable : true
85
- []
86
- # then, if exit code looks okay, process an output
87
- else if exitCode is 101 or exitCode is 0
88
- # in dev mode show message boxes with output
89
- showDevModeWarning = (stream , message ) ->
90
- atom .notifications .addWarning " Output from #{ stream} while linting" ,
91
- detail : " #{ message} "
92
- description : " This is shown because Atom is running in dev-mode and probably not an actual error"
93
- dismissable : true
94
- if do atom .inDevMode
95
- showDevModeWarning (' stderr' , stderr) if stderr
96
- showDevModeWarning (' stdout' , stdout) if stdout
97
-
98
- # call a needed parser
99
- messages = unless ableToJSONErrors
100
- @ parse stderr
101
- else
102
- @ parseJSON stderr
103
-
104
- # correct file paths
105
- messages .forEach (message) ->
106
- if ! (path .isAbsolute message .filePath )
107
- message .filePath = path .join curDir, message .filePath
108
- messages
109
- else
110
- # whoops, we're in trouble -- let's output as much as we can
111
- atom .notifications .addError " Failed to run #{ command} with exit code #{ exitCode} " ,
112
- detail : " with args:\n #{ args .join (' ' )} \n See console for more information"
113
- dismissable : true
114
- console .log " stdout:"
115
- console .log stdout
116
- console .log " stderr:"
117
- console .log stderr
118
- []
119
- .catch (error) ->
120
- console .log error
121
- atom .notifications .addError " Failed to run #{ command} " ,
122
- detail : " #{ error .message } "
58
+ @ initCmd (textEditor .getPath ()).then (result) =>
59
+ [cmd_res , errorMode ] = result
60
+ [file , cmd ] = cmd_res
61
+ env = JSON .parse JSON .stringify process .env
62
+ curDir = path .dirname file
63
+ cwd = curDir
64
+ command = cmd[0 ]
65
+ args = cmd .slice 1
66
+ env .PATH = path .dirname (cmd[0 ]) + path .delimiter + env .PATH
67
+
68
+ # we set flags only for intermediate json support
69
+ if errorMode == errorModes .FLAGS_JSON_CARGO
70
+ if ! env .RUSTFLAGS ? or ! (env .RUSTFLAGS .indexOf (' --error-format=json' ) >= 0 )
71
+ additional = if env .RUSTFLAGS ? then ' ' + env .RUSTFLAGS else ' '
72
+ env .RUSTFLAGS = ' --error-format=json' + additional
73
+
74
+ sb_exec .exec (command, args, {env : env, cwd : cwd, stream : ' both' })
75
+ .then (result) =>
76
+ {stdout , stderr , exitCode } = result
77
+ # first, check if an output says specified features are invalid
78
+ if stderr .indexOf (' does not have these features' ) >= 0
79
+ atom .notifications .addError " Invalid specified features" ,
80
+ detail : " #{ stderr} "
123
81
dismissable : true
124
82
[]
125
-
126
- parseJSON : (output ) =>
127
- elements = []
128
- results = output .split ' \n '
129
- for result in results
130
- if result .startsWith ' {'
131
- input = JSON .parse result .trim ()
132
- continue unless input .spans
133
- primary_span = input .spans .find (span) -> span .is_primary
134
- continue unless primary_span
135
- range = [
136
- [primary_span .line_start - 1 , primary_span .column_start - 1 ],
137
- [primary_span .line_end - 1 , primary_span .column_end - 1 ]
138
- ]
139
- input .level = ' error' if input == ' fatal error'
140
- element =
141
- type : input .level
142
- message : input .message
143
- file : primary_span .file_name
144
- range : range
145
- children : input .children
146
- for span in input .spans
147
- unless span .is_primary
148
- element .children .push
149
- message : span .label
150
- range : [
151
- [span .line_start - 1 , span .column_start - 1 ],
152
- [span .line_end - 1 , span .column_end - 1 ]
153
- ]
154
- elements .push element
155
- @ buildMessages (elements)
156
-
157
- parse : (output ) =>
158
- elements = []
159
- XRegExp .forEach output, @pattern , (match ) ->
160
- if match .from_col == match .to_col
161
- match .to_col = parseInt (match .to_col ) + 1
162
- range = [
163
- [match .from_line - 1 , match .from_col - 1 ],
164
- [match .to_line - 1 , match .to_col - 1 ]
165
- ]
166
- level = if match .error then ' error'
167
- else if match .warning then ' warning'
168
- else if match .info then ' info'
169
- else if match .trace then ' trace'
170
- else if match .note then ' note'
171
- element =
172
- type : level
173
- message : match .message
174
- file : match .file
175
- range : range
176
- elements .push element
177
- @ buildMessages elements
178
-
179
- buildMessages : (elements ) =>
180
- messages = []
181
- lastMessage = null
182
- for element in elements
183
- switch element .type
184
- when ' info' , ' trace' , ' note'
185
- # Add only if there is a last message
186
- if lastMessage
187
- lastMessage .trace or= []
188
- lastMessage .trace .push
189
- type : " Trace"
190
- text : element .message
191
- filePath : element .file
192
- range : element .range
193
- when ' warning'
194
- # If the message is warning and user enabled disabling warnings
195
- # Check if this warning is disabled
196
- if @disabledWarnings and @disabledWarnings .length > 0
197
- messageIsDisabledLint = false
198
- for disabledWarning in @disabledWarnings
199
- # Find a disabled lint in warning message
200
- if element .message .indexOf (disabledWarning) >= 0
201
- messageIsDisabledLint = true
202
- lastMessage = null
203
- break
204
- if not messageIsDisabledLint
205
- lastMessage = @ constructMessage " Warning" , element
206
- messages .push lastMessage
83
+ # then, if exit code looks okay, process an output
84
+ else if exitCode is 101 or exitCode is 0
85
+ # in dev mode show message boxes with output
86
+ showDevModeWarning = (stream , message ) ->
87
+ atom .notifications .addWarning " Output from #{ stream} while linting" ,
88
+ detail : " #{ message} "
89
+ description : " This is shown because Atom is running in dev-mode and probably not an actual error"
90
+ dismissable : true
91
+ if do atom .inDevMode
92
+ showDevModeWarning (' stderr' , stderr) if stderr
93
+ showDevModeWarning (' stdout' , stdout) if stdout
94
+
95
+ # call a needed parser
96
+ output = errorMode .neededOutput (stdout, stderr)
97
+ messages = errorMode .parse output, @disabledWarnings
98
+
99
+ # correct file paths
100
+ messages .forEach (message) ->
101
+ if ! (path .isAbsolute message .filePath )
102
+ message .filePath = path .join curDir, message .filePath
103
+ messages
207
104
else
208
- lastMessage = @ constructMessage " Warning" , element
209
- messages .push lastMessage
210
- when ' error' , ' fatal error'
211
- lastMessage = @ constructMessage " Error" , element
212
- messages .push lastMessage
213
- return messages
214
-
215
- constructMessage : (type , element ) ->
216
- message =
217
- type : type
218
- text : element .message
219
- filePath : element .file
220
- range : element .range
221
- # children exists only in JSON messages
222
- if element .children
223
- message .trace = []
224
- for children in element .children
225
- message .trace .push
226
- type : " Trace"
227
- text : children .message
228
- filePath : element .file
229
- range : children .range or element .range
230
- message
231
-
232
- initCmd : (editingFile , ableToJSONErrors ) =>
233
- rustcArgs = switch @rustcBuildTest
234
- when true then [' --cfg' , ' test' , ' -Z' , ' no-trans' , ' --color' , ' never' ]
235
- else [' -Z' , ' no-trans' , ' --color' , ' never' ]
236
- cargoArgs = switch @cargoCommand
237
- when ' check' then [' check' ]
238
- when ' test' then [' test' , ' --no-run' ]
239
- when ' rustc' then [' rustc' , ' -Zno-trans' , ' --color' , ' never' ]
240
- when ' clippy' then [' clippy' ]
241
- else [' build' ]
242
-
105
+ # whoops, we're in trouble -- let's output as much as we can
106
+ atom .notifications .addError " Failed to run #{ command} with exit code #{ exitCode} " ,
107
+ detail : " with args:\n #{ args .join (' ' )} \n See console for more information"
108
+ dismissable : true
109
+ console .log " stdout:"
110
+ console .log stdout
111
+ console .log " stderr:"
112
+ console .log stderr
113
+ []
114
+ .catch (error) ->
115
+ console .log error
116
+ atom .notifications .addError " Failed to run #{ command} " ,
117
+ detail : " #{ error .message } "
118
+ dismissable : true
119
+ []
120
+
121
+ initCmd : (editingFile ) =>
122
+ curDir = path .dirname editingFile
243
123
cargoManifestPath = @ locateCargo path .dirname editingFile
244
124
if not @useCargo or not cargoManifestPath
245
- Promise .resolve ().then () =>
246
- cmd = [@rustcPath ]
247
- .concat rustcArgs
248
- if cargoManifestPath
249
- cmd .push ' -L'
250
- cmd .push path .join path .dirname (cargoManifestPath), @cargoDependencyDir
251
- compilationFeatures = @ compilationFeatures (false )
252
- cmd = cmd .concat compilationFeatures if compilationFeatures
253
- cmd = cmd .concat [editingFile]
254
- cmd = cmd .concat [' --error-format=json' ] if ableToJSONErrors
255
- [editingFile, cmd]
125
+ @ decideErrorMode (curDir, ' rustc' ).then (mode) =>
126
+ mode .buildArguments (this , [editingFile, cargoManifestPath]).then (cmd) =>
127
+ [cmd, mode]
256
128
else
257
- @ buildCargoPath (@cargoPath ).then (cmd) =>
258
- compilationFeatures = @ compilationFeatures (true )
259
- cmd = cmd
260
- .concat cargoArgs
261
- .concat [' -j' , @jobsNumber ]
262
- cmd = cmd .concat compilationFeatures if compilationFeatures
263
- cmd = cmd .concat [' --manifest-path' , cargoManifestPath]
264
- [cargoManifestPath, cmd]
129
+ @ decideErrorMode (curDir, ' cargo' ).then (mode) =>
130
+ mode .buildArguments (this , cargoManifestPath).then (cmd) =>
131
+ [cmd, mode]
265
132
266
133
compilationFeatures : (cargo ) =>
267
134
if @specifiedFeatures .length > 0
@@ -273,19 +140,32 @@ class LinterRust
273
140
result .push [' --cfg' , " feature=\" #{ f} \" " ]
274
141
result
275
142
276
- ableToJSONErrors : (curDir ) =>
143
+ decideErrorMode : (curDir , commandMode ) =>
277
144
# current dir is set to handle overrides
278
-
279
145
sb_exec .exec (@rustcPath , [' --version' ], {stream : ' stdout' , cwd : curDir, stdio : ' pipe' }).then (stdout) =>
280
- console .log stdout
281
146
try
282
147
match = XRegExp .exec (stdout, @patternRustcVersion )
283
- if match and match .nightly and match .date > ' 2016-08-08'
284
- true
285
- else if match and not match .nightly and semver .gte (match .version , ' 1.12.0' )
286
- true
148
+ if match
149
+ nightlyWithJSON = match .nightly and match .date > ' 2016-08-08'
150
+ stableWithJSON = not match .nightly and semver .gte (match .version , ' 1.12.0' )
151
+ canUseIntermediateJSON = nightlyWithJSON or stableWithJSON
152
+ switch commandMode
153
+ when ' cargo'
154
+ canUseProperCargoJSON = match .nightly and match .date > ' 2016-10-10'
155
+ if canUseProperCargoJSON
156
+ errorModes .JSON_CARGO
157
+ # this mode is used only through August till October, 2016
158
+ else if canUseIntermediateJSON
159
+ errorModes .FLAGS_JSON_CARGO
160
+ else
161
+ errorModes .OLD_CARGO
162
+ when ' rustc'
163
+ if canUseIntermediateJSON
164
+ errorModes .JSON_RUSTC
165
+ else
166
+ errorModes .OLD_RUSTC
287
167
else
288
- false
168
+ throw ' rustc returned inapplicable result: ' + stdout
289
169
290
170
locateCargo : (curDir ) =>
291
171
root_dir = if / ^ win/ .test process .platform then / ^ . :\\ $ / else / ^ \/ $ /
@@ -296,24 +176,4 @@ class LinterRust
296
176
directory = path .resolve path .join (directory, ' ..' )
297
177
return false
298
178
299
- buildCargoPath : (cargoPath ) =>
300
- @ usingMultitoolForClippy ().then (canUseMultirust) =>
301
- if @cargoCommand == ' clippy' and canUseMultirust .result
302
- [canUseMultirust .tool , ' run' , ' nightly' , ' cargo' ]
303
- else
304
- [cargoPath]
305
-
306
- usingMultitoolForClippy : () =>
307
- # Try to use rustup
308
- sb_exec .exec ' rustup' , [' --version' ], {ignoreExitCode : true }
309
- .then ->
310
- result : true , tool : ' rustup'
311
- .catch ->
312
- # Try to use odler multirust at least
313
- sb_exec .exec ' multirust' , [' --version' ], {ignoreExitCode : true }
314
- .then ->
315
- result : true , tool : ' multirust'
316
- .catch ->
317
- result : false
318
-
319
179
module .exports = LinterRust
0 commit comments