@@ -2,15 +2,11 @@ fs = require 'fs'
2
2
path = require ' path'
3
3
XRegExp = require ' xregexp'
4
4
semver = require ' semver'
5
- sb_exec = require ' sb-exec'
6
5
{CompositeDisposable } = require ' atom'
7
-
6
+ atom_linter = require ' atom-linter'
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"
@@ -55,213 +51,88 @@ class LinterRust
55
51
(specifiedFeatures ) =>
56
52
@specifiedFeatures = specifiedFeatures
57
53
54
+ @subscriptions .add atom .config .observe ' linter-rust.allowedToCacheVersions' ,
55
+ (allowedToCacheVersions ) =>
56
+ @allowedToCacheVersions = allowedToCacheVersions
57
+
58
58
destroy : ->
59
59
do @subscriptions .dispose
60
60
61
61
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 } "
62
+ @ initCmd (textEditor .getPath ()).then (result) =>
63
+ [cmd_res , errorMode ] = result
64
+ [file , cmd ] = cmd_res
65
+ env = JSON .parse JSON .stringify process .env
66
+ curDir = path .dirname file
67
+ cwd = curDir
68
+ command = cmd[0 ]
69
+ args = cmd .slice 1
70
+ env .PATH = path .dirname (cmd[0 ]) + path .delimiter + env .PATH
71
+
72
+ # we set flags only for intermediate json support
73
+ if errorMode == errorModes .FLAGS_JSON_CARGO
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
+
78
+ atom_linter .exec (command, args, {env : env, cwd : cwd, stream : ' both' })
79
+ .then (result) =>
80
+ {stdout , stderr , exitCode } = result
81
+ # first, check if an output says specified features are invalid
82
+ if stderr .indexOf (' does not have these features' ) >= 0
83
+ atom .notifications .addError " Invalid specified features" ,
84
+ detail : " #{ stderr} "
123
85
dismissable : true
124
86
[]
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
87
+ # then, if exit code looks okay, process an output
88
+ else if exitCode is 101 or exitCode is 0
89
+ # in dev mode show message boxes with output
90
+ showDevModeWarning = (stream , message ) ->
91
+ atom .notifications .addWarning " Output from #{ stream} while linting" ,
92
+ detail : " #{ message} "
93
+ description : " This is shown because Atom is running in dev-mode and probably not an actual error"
94
+ dismissable : true
95
+ if do atom .inDevMode
96
+ showDevModeWarning (' stderr' , stderr) if stderr
97
+ showDevModeWarning (' stdout' , stdout) if stdout
98
+
99
+ # call a needed parser
100
+ output = errorMode .neededOutput (stdout, stderr)
101
+ messages = errorMode .parse output, {@disabledWarnings , textEditor}
102
+
103
+ # correct file paths
104
+ messages .forEach (message) ->
105
+ if ! (path .isAbsolute message .filePath )
106
+ message .filePath = path .join curDir, message .filePath
107
+ messages
207
108
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
-
243
- cargoManifestPath = @ locateCargo path .dirname editingFile
109
+ # whoops, we're in trouble -- let's output as much as we can
110
+ atom .notifications .addError " Failed to run #{ command} with exit code #{ exitCode} " ,
111
+ detail : " with args:\n #{ args .join (' ' )} \n See console for more information"
112
+ dismissable : true
113
+ console .log " stdout:"
114
+ console .log stdout
115
+ console .log " stderr:"
116
+ console .log stderr
117
+ []
118
+ .catch (error) ->
119
+ console .log error
120
+ atom .notifications .addError " Failed to run #{ command} " ,
121
+ detail : " #{ error .message } "
122
+ dismissable : true
123
+ []
124
+
125
+ initCmd : (editingFile ) =>
126
+ curDir = path .dirname editingFile
127
+ cargoManifestPath = @ locateCargo curDir
244
128
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]
129
+ @ decideErrorMode (curDir, ' rustc' ).then (mode) =>
130
+ mode .buildArguments (this , [editingFile, cargoManifestPath]).then (cmd) =>
131
+ [cmd, mode]
256
132
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]
133
+ @ decideErrorMode (curDir, ' cargo' ).then (mode) =>
134
+ mode .buildArguments (this , cargoManifestPath).then (cmd) =>
135
+ [cmd, mode]
265
136
266
137
compilationFeatures : (cargo ) =>
267
138
if @specifiedFeatures .length > 0
@@ -273,19 +144,41 @@ class LinterRust
273
144
result .push [' --cfg' , " feature=\" #{ f} \" " ]
274
145
result
275
146
276
- ableToJSONErrors : (curDir ) =>
277
- # current dir is set to handle overrides
147
+ decideErrorMode : (curDir , commandMode ) =>
148
+ # error mode is cached to avoid delays
149
+ if @cachedErrorMode ? and @allowedToCacheVersions
150
+ Promise .resolve ().then () =>
151
+ @cachedErrorMode
152
+ else
153
+ # current dir is set to handle overrides
154
+ atom_linter .exec (@rustcPath , [' --version' ], {cwd : curDir}).then (stdout) =>
155
+ try
156
+ match = XRegExp .exec (stdout, @patternRustcVersion )
157
+ if match
158
+ nightlyWithJSON = match .nightly and match .date > ' 2016-08-08'
159
+ stableWithJSON = not match .nightly and semver .gte (match .version , ' 1.12.0' )
160
+ canUseIntermediateJSON = nightlyWithJSON or stableWithJSON
161
+ switch commandMode
162
+ when ' cargo'
163
+ canUseProperCargoJSON = match .nightly and match .date >= ' 2016-10-10'
164
+ if canUseProperCargoJSON
165
+ errorModes .JSON_CARGO
166
+ # this mode is used only through August till October, 2016
167
+ else if canUseIntermediateJSON
168
+ errorModes .FLAGS_JSON_CARGO
169
+ else
170
+ errorModes .OLD_CARGO
171
+ when ' rustc'
172
+ if canUseIntermediateJSON
173
+ errorModes .JSON_RUSTC
174
+ else
175
+ errorModes .OLD_RUSTC
176
+ else
177
+ throw ' rustc returned unexpected result: ' + stdout
178
+ .then (result) =>
179
+ @cachedErrorMode = result
180
+ result
278
181
279
- sb_exec .exec (@rustcPath , [' --version' ], {stream : ' stdout' , cwd : curDir, stdio : ' pipe' }).then (stdout) =>
280
- console .log stdout
281
- try
282
- 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
287
- else
288
- false
289
182
290
183
locateCargo : (curDir ) =>
291
184
root_dir = if / ^ win/ .test process .platform then / ^ . :\\ $ / else / ^ \/ $ /
@@ -296,24 +189,4 @@ class LinterRust
296
189
directory = path .resolve path .join (directory, ' ..' )
297
190
return false
298
191
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
192
module .exports = LinterRust
0 commit comments