@@ -2,15 +2,11 @@ fs = require 'fs'
22path = require ' path'
33XRegExp = require ' xregexp'
44semver = require ' semver'
5- sb_exec = require ' sb-exec'
65{CompositeDisposable } = require ' atom'
7-
6+ atom_linter = require ' atom-linter'
7+ errorModes = require ' ./mode'
88
99class 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' )
1410 patternRustcVersion : XRegExp (' rustc (?<version>1.\\ d+.\\ d+)(?:(?:-(?<nightly>nightly)|(?:[^\\ s]+))? \
1511 \\ ((?:[^\\ s]+) (?<date>\\ d{4}-\\ d{2}-\\ d{2})\\ ))?' )
1612 cargoDependencyDir : " target/debug/deps"
@@ -55,213 +51,88 @@ class LinterRust
5551 (specifiedFeatures ) =>
5652 @specifiedFeatures = specifiedFeatures
5753
54+ @subscriptions .add atom .config .observe ' linter-rust.allowedToCacheVersions' ,
55+ (allowedToCacheVersions ) =>
56+ @allowedToCacheVersions = allowedToCacheVersions
57+
5858 destroy : ->
5959 do @subscriptions .dispose
6060
6161 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} "
12385 dismissable : true
12486 []
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
207108 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
244128 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]
256132 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]
265136
266137 compilationFeatures : (cargo ) =>
267138 if @specifiedFeatures .length > 0
@@ -273,19 +144,41 @@ class LinterRust
273144 result .push [' --cfg' , " feature=\" #{ f} \" " ]
274145 result
275146
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
278181
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
289182
290183 locateCargo : (curDir ) =>
291184 root_dir = if / ^ win/ .test process .platform then / ^ . :\\ $ / else / ^ \/ $ /
@@ -296,24 +189,4 @@ class LinterRust
296189 directory = path .resolve path .join (directory, ' ..' )
297190 return false
298191
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-
319192module .exports = LinterRust
0 commit comments