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

Added a proper support for cargo --message-format json #84

Merged
merged 5 commits into from
Oct 24, 2016

Conversation

White-Oak
Copy link
Member

Massively refactored code, so the error mode behaviour is less dependent on conditions and more polymorphic.

I've tested this in all five modes that linter-rust currently supports:

  • Old rustc plain-text messages
  • Old cargo plain-text messages
  • Rustc json errors
  • Intermediate cargo json errors, enabled by RUSTFLAGS, that caused several problems and only enabled for a short ammount of time from AUgust to October 2016
  • Proper cargo json errors enabled by --message-format json, that are introduced by this PR

In short, I split the linter in two files: linter-rust.coffee and mode.coffee. The latter is now responsible on mode-specific interactions such as error parsing, arguments generating.

Fixes #77.

As far as I tested it, it works, but I, probably, need some review on code.

Massively refactored code, so the error mode behaviour is less dependent on conditions and more polymorphic.
@White-Oak
Copy link
Member Author

@Arcanemagus if you have time and wish, can you please review code I wrote, just as you did at #74?

@Arcanemagus
Copy link
Member

Sorry, just saw this. Looking over it now!

Copy link
Member

@Arcanemagus Arcanemagus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's what I spotted looking through this, a lot of it is notes or ideas for future/further changes, there are what looks like some 🐛's though.

.toEqual('a\r\nb')

multi = linter.parse('a:1:2: 3:4 error: a\n\rb\n\rx:1:2: 3:4 error: asd\r\n')
multi = linter.parse('a:1:2: 3:4 error: a\n\rb\n\rx:1:2: 3:4 error: asd\r\n', [])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 I'm guessing this should be errorModes.OLD_RUSTC.parse.

# current dir is set to handle overrides

sb_exec.exec(@rustcPath, ['--version'], {stream: 'stdout', cwd: curDir, stdio: 'pipe'}).then (stdout) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 stdout is the default stream, there's no need to explicitly state it here. The same goes for stdio as 'pipe' is the default.

else
false
throw 'rustc returned inapplicable result: ' + stdout
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Does "inapplicable" work here? Maybe "unparseable"?

# current dir is set to handle overrides

sb_exec.exec(@rustcPath, ['--version'], {stream: 'stdout', cwd: curDir, stdio: 'pipe'}).then (stdout) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 This will work just fine, but to avoid calling rustc --version for every lint call, you might want to think about caching the result. For an example here is what I recently put together to cache the PHPCS version as the behavior allowed there is also heavily dependent on version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same can be said for other "check calls", such as here

[]

initCmd: (editingFile) =>
curDir = path.dirname editingFile
cargoManifestPath = @locateCargo path.dirname editingFile
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐎 Use the curDir variable you define on the line above here, no need to call path.dirname(editingFile) again.

elements = []
XRegExp.forEach output, pattern, (match) ->
if match.from_col == match.to_col
match.to_col = parseInt(match.to_col) + 1
Copy link
Member

@Arcanemagus Arcanemagus Oct 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Situations like this are where atom-linter::rangeFromLineNumber is great as it will automatically determine a range highlighting the next "word", as defined by the language. So you can feed it a single point and it will generate a better range than just the single character. It would require some refactoring though as it requires access to the TextEditor of the file being linted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Arcanemagus if I add atom-linter as a dependency, I should probably remove sb-exec, since it's included, should I?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, atom-linter::exec is a wrapper for sb-exec so there is no need to include it directly.

parseJsonMessages = (messages, disabledWarnings) ->
elements = []
for input in messages
continue unless input and input.spans
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Linter messages don't (currently) require a range, is this ignoring potentially useful messages just because they don't have a span?

[primary_span.line_start - 1, primary_span.column_start - 1],
[primary_span.line_end - 1, primary_span.column_end - 1]
]
input.level = 'error' if input == 'fatal error'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 Should input == 'fatal error' be input.message == 'fatal error'?

neededOutput: (stdout, stderr) ->
stdout

parse: (output, disabledWarnings) ->
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 Can the functionality here be merged into parseJsonOutput? They are almost identical.


buildRustcArguments = (linter, paths) ->
[editingFile, cargoManifestPath] = paths
Promise.resolve().then () =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🐛 It looks like you are trying to use this as a way to return a Promise, you should just go directly there:
new Promise (resolve, reject) =>

I'd consider this an optional change though I guess, as it makes the CoffeeScript of the function even worse than it is from the start 😛.

@White-Oak
Copy link
Member Author

@Arcanemagus huge thanks for the big and helpful review!

The tricky part, although, is caching results, as a user might switch tools on the fly, I've added a simple caching wrapper with TTL, that survives only TTL lints.

@Arcanemagus
Copy link
Member

That's why that was just a suggestion and not something that needed to be addressed. As I said it would work just fine as is 😉.

@Arcanemagus
Copy link
Member

Hmm, so the way I was caching it in other providers I cached the version based on the path to the executable, I was thinking of something along the lines of caching the result here based on the current directory as that should handle changing executables by directory which is what most "executable version management" systems do.

Would that not work here? Or does the version get changed often for the same directory?

@zofrex
Copy link

zofrex commented Oct 22, 2016

I hope it's not rude to butt in, I was trying to find a solution for my cargo builds being invalidated all the time, stumbled across this PR, and did some unsolicited testing :)

canUseIntermediateJSON = nightlyWithJSON or stableWithJSON
switch commandMode
when 'cargo'
canUseProperCargoJSON = match.nightly and match.date > '2016-10-10'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a really small deal but I happened to be running 2016-10-10 and it does have "--message-support". Is this the right version number to key off?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, the right starting version is probably 10-08 or 10-09 since it was merged 15 days ago in cargo. Thanks for the help, I 'll get this fixed :)

stdout

parse: (output, options) ->
options.additionalFilter = (json) ->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a bug, on my machine it was filtering out 100% of errors returned from the compiler. I think that either "json" here should be "input" to match below, or vice versa.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops, thanks a lot! I wonder how did I miss that one. I will need to write better tests once this is finished.

@White-Oak
Copy link
Member Author

@Arcanemagus well, it's not that common to change toolchains in Rust, but it's something that one can and expect being able to easily do. For example, consider someone coding a library with a stable compiler in Atom with linter-rust, and then switching over to a beta or a nightly toolchain (that's where the override is being set for a project, and toolchains' versions are changed as a consequence) to check if there are any warnings considering any possible stabilizations or deprecations.

Hmm, so the way I was caching it in other providers I cached the version based on the path to the executable, I was thinking of something along the lines of caching the result here based on the current directory as that should handle changing executables by directory which is what most "executable version management" systems do.

Would that not work here? Or does the version get changed often for the same directory?

There is, actually, no precedent of 'per-directory' toolchain swapping in Rust, as far as I'm aware of.
In Rust you can, of course, set an overriding toolchain for every directory you'd like, but that's a really rare use case. Most of work in Rust is done on a 'per-project' basis, so setting an override for some subdirectory of that project does not really make much sense in language ecosystem terms.

On the other side, I can imagine someone making a large directory of subdirectories that have one main.rs each and not using Rust's package manager (cargo), but rustc compiler directly to compile and run them. This is where a more fine-grained caching of toolchains' versions can be useful, as one can set overrides on several subdirectories, while not setting that override on others.
However, the ability to cache versions 'per-directory' can be added to any caching mechanism, as long as it exists, that's something that is need to be reconsidered.

All in all, there are several ways to handle caching:

  1. Don't cache at all. That's simple and that's how it works now in master. The downside is increased performance hit per lint action.
  2. Cache 'forever' (as in 'until editor is closed'). This is simple, and I think, what should be actually done, but thee may be an issue of people switching toolchains -- that would force them to reopen Atom every time
    1. Maybe there is a point in introducing an option in rust-linter, say, Cache toolchains' versions, disabling which would force code to check versions every time.
  3. Cache using several heuristics like the caching system in this PR that uses TTL. When I wrote that system, initially I thought it was really great and would solve defined above issues, but now I understand, that it's not a real solution, instead it's being harder to reason about for a user (e.g. "why does linter-rust not lint after I changed a toolchain's version, but then, suddenly starts to lint?").

What do you think?

@Arcanemagus
Copy link
Member

I would vote for option 2, since it sounds unlikely that it would change during a session of Atom being open, at least that's the reasoning I went with for linter-phpcs 😛.

Maybe a note in the readme that if the use changes the version a restart of Atom will be required to catch any additional features (un)available in the new version?

Just wanted to say thanks for all the work you've been putting into this provider btw!

@colin-kiegel
Copy link
Contributor

colin-kiegel commented Oct 24, 2016

I've been following this discussion. :-)

I think switching the toolchain during a session is not so rare. I, for example, recently worked on a rust macro library which is supposed to run on rust-stable. However all the nice macro-debugging tools are only available in rust-nightly. Whenever I discovered a hard bug with rust-stable I switched to rust-nightly until I fixed it - and back again, repeatedly. All during one atom session.

I think option 2.1 would be a good start, i.e. let the user decide if caching is enabled. From this point on any kind of heuristic can still be added in the future.

@zofrex
Copy link

zofrex commented Oct 24, 2016

What is the performance hit of not caching?

@White-Oak
Copy link
Member Author

@zofrex Well, to be honest, it's not that big comparing to compilation times :) But It's more a future-proofing thing. Rustc is constantly improving and we are about to have incremental compilation soon. As an example of compilation times improving see this issue (in short, the compilation time went from 30 secs to 5 in a time span of year).

Going into details, linter-rust calls two/three shell commands per lint process (rustup --version, multirust --version if you have no rustup installed, and rustc --version). Calling shell commands means starting a new process etc and is often compared to performing an IO operations -- it's better to avoid them.

@White-Oak
Copy link
Member Author

@Arcanemagus it's me who should thank you for your reviews and advices! (: Thanks for helping me out here and there.

Kinda off-topic, but I'd ask you another linter related question. I understand that most linters do not have big timings on performing a lint, but as you've probably seen in the discussions Rust does suffer from big compilation times.
And considering that, have there ever been a proposal on swapping this (that's at the bottom of the editor):
spectacle lh2471
with a 'Linting' label while the linting is in process? If yes, why has it been turned down? If no, do you think it makes sense to propose that one? Since it would really help linter-rust's users, since it is sometimes really hard to understand, if a linting is undergoing, or is there something wrong.

@White-Oak
Copy link
Member Author

@colin-kiegel thanks for the input! Yes, I think it's what we should go with. I've been reluctant to add another option, since I know people are sometimes confused with a number of options, but I hope 10 options is not a really big deal..

@White-Oak
Copy link
Member Author

I've enabled 'caching forever' to be a default choice with an option in settings called Allowed To Cache Versions.

@Arcanemagus if you have no other notices, I believe you can mark your review as a positive. Thanks again for your review.

@Arcanemagus
Copy link
Member

@White-Oak Linter v2 incorporates the busy-signal package, which has a nice indicator when it is currently running a lint: image

In fact, it completely removes the "No issues" text. you can check it out following the instructions here: https://github.com/steelbrain/linter-ui-default

Copy link
Member

@Arcanemagus Arcanemagus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@White-Oak White-Oak merged commit 7da1096 into AtomLinter:master Oct 24, 2016
@White-Oak
Copy link
Member Author

@gmist I hope you are not against that I merged this.

@gmist
Copy link
Member

gmist commented Oct 24, 2016

of course not against

@colin-kiegel
Copy link
Contributor

PS: I would suggest to default to 'no caching' to be on the safe side.
People who want more juice will probably take some time tweaking settings
anyway.

Oak notifications@github.com schrieb am Mo., 24. Okt. 2016, 22:36:

@colin-kiegel https://github.com/colin-kiegel thanks for the input!
Yes, I think it's what we should go with. I've been reluctant to add
another option, since I know people are sometimes confused with a number of
options, but I hope 10 options is not a really big deal..


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#84 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AHVbstH-jQqVILZTxuCmAennQ6O04IK4ks5q3RazgaJpZM4KX8Y6
.

@White-Oak
Copy link
Member Author

@colin-kiegel default settings are rarely changed, if making 'no caching' be the default choice, then why use caching anyway.

@colin-kiegel
Copy link
Contributor

@White-Oak I guess in the end it's a matter of taste. Its ok for me to change the setting, but I would still suggest at least add a little warning in the settings description, that the rust linter will not detect a change in toolchain until restart. Again of course it's your choice.

But this discussion reminds me, there may be another "hacky" approach instead of caching. You mentioned that you want to avoid spawning processes for shell commands. It may be possible to exchange a handful of small shell calls plus logic and bundle them into a small shell script instead, which can be passed to only one shell call. Of course the small shell script needs to be interpreted by the shell, but the spawning of processes can be saved. However the details of this approach will most likely be very OS-specific. If it is possible at all it would probably be a bit brittle - but I still wanted to share the thought, just in case... ^^

PS: Thanks for the great work.

@White-Oak
Copy link
Member Author

@colin-kiegel there is one :)
spectacle lh1872

@AaronM04
Copy link

Maybe this isn't the right place to ask, but:

Are the changes in this PR present in version 0.7.0? I am still experiencing the #77 problem with rust nightly from yesterday, and version 0.7.0, with Atom 1.11.2 on Linux x86_64. I just discovered a workaround, which is to compile with --release in the terminal.

@White-Oak
Copy link
Member Author

@AaronM04 thanks for the report! This is really weird, and really strange! Can you please as well post a version of rustc?
I will try my best to investigate that!

@AaronM04
Copy link

@White-Oak

$ rustc --version
rustc 1.12.1 (d4f39402a 2016-10-19)

@AaronM04
Copy link

@White-Oak
Looks like I wasn't using nightly after all.

@White-Oak
Copy link
Member Author

@AaronM04 yes, it is a stable from when JSON errors were only supported via RUSTCFLAGS. Hope it cleared everything out. Latest stable (1.13), beta and nightly version all support proper JSON errors' support. Hope it helps :)

@AaronM04
Copy link

@White-Oak I switched to the real nightly and everything works. Thanks!

@White-Oak White-Oak deleted the cargo-json branch November 22, 2016 12:59
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants