-
Notifications
You must be signed in to change notification settings - Fork 141
Use ESLint's CLIEngine API rather than the command line (fixes #797) #873
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than the rather funny decisions git
made on the directories it was making the cli.js
-> app.js
moves to/from, this looks great to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a few comments and questions, but overall this looks pretty solid. There's only one thing I think we have to address for sure (the check if fix
was successful).
I'm actually somewhat surprised by how few changes you had to make here. For some reason I assumed it would be a bigger diff. Nice going.
src/worker.js
Outdated
} else if (type === 'fix') { | ||
job.response = fixJob(argv, eslint) | ||
job.response = fixJob({ cliEngineOptions, contents, eslint, filePath }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: Is the reason you're passing a currently-always-undefined contents
into fixJob
because you expect that in the future we would pass unsaved text into the fix job? For some reason I thought that ESLint wouldn't allow --fix
for stdin, but maybe it does when using executeOnText
? Could you talk a little about your thoughts here on passing contents
to fixJob
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it's because I didn't realize contents
was always undefined
. 😄
It's true that ESLint doesn't allow --fix
from stdin on the command line, but this isn't a problem when calling executeOnText
because it accepts a filename
argument. So using executeOnText
followed by CLIEngine.outputFixes
would allow us to pass unsaved text (if we wanted to do that).
Notably, executeOnText
also accepts a warnIgnored argument, which can be used to disable "file ignored because of matching ignore pattern" messages. If we switch to using contents
and used executeOnText
instead of executeOnFiles
, we could also resolve the issue that you brought up in #873 (comment) without needing to bring back the message list.
(I suppose another way to do this would be to read the file from the filesystem ourselves and call executeOnText
on it to get rid of the ignore warnings.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should explore using executeOnText
for fixes, but in a separate PR. I'd like to keep this PR small in scope so we're not changing too much at once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, in that case should I add back the list of ignore messages for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be my preference, yes.
src/worker.js
Outdated
|
||
eslint.CLIEngine.outputFixes(report) | ||
|
||
if (!report.results.length || !report.results[0].messages.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, this won't work if a user tries to fix an ignored file. We will get back a warning from ESLint that the file is ignored, and therefore the message displayed will be:
Linter-ESLint: Fix attempt complete, but linting errors remain.
which is less than ideal. I really wanted to be happy about deleting ignoredMessages
, but I think we still need it, at least for this situation. We could even be smart and show a meaningful message if we determine that the file the user tried to fix is being ignored.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we can move to executeOnText
for fix jobs at some point and turn off warnIgnored
, as you mentioned in #873 (comment), but for now let's keep the ignoredMessages
and use them here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm understanding this correctly in that executeOnText
works for this, couldn't we just send in contents
from the fix jobs? We already have a reference to the editor in both places one is requested so this should be pretty simple.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind, just read the comments below, moving that to a separate PR works for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's the proposal. I also think it would be easy, but I'd like it to be done in a separate PR to isolate the changes.
job.response = lintJob(argv, contents, eslint, configPath, config) | ||
const noProjectConfig = (configPath === null || isConfigAtHomeRoot(configPath)) | ||
if (noProjectConfig && config.disableWhenNoEslintConfig) { | ||
job.response = [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call moving this check down here, it should definitely apply to lint jobs and fix jobs. 👍
src/worker.js
Outdated
@@ -54,16 +39,19 @@ create().onRequest('job', ({ contents, type, config, filePath, projectPath, rule | |||
const configPath = Helpers.getConfigPath(fileDir) | |||
const relativeFilePath = Helpers.getRelativePath(fileDir, filePath, config) | |||
|
|||
const argv = Helpers.getArgv(type, config, rules, relativeFilePath, fileDir, configPath) | |||
const cliEngineOptions = | |||
Helpers.getCLIEngineOptions(type, config, rules, relativeFilePath, fileDir, configPath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how @Arcanemagus feels about this one, but personally I would prefer avoiding the long-line error by formatting it as:
const cliEngineOptions = Helpers.getCLIEngineOptions(
type, config, rules, relativeFilePath, fileDir, configPath
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's how I would prefer it as well, but the styling in this project already has issues as far as I'm concerned so I just left it for a future update of the style 😛.
src/worker.js
Outdated
'File ignored by default. Use "--ignore-pattern \'!bower_components/*\'" to override.', | ||
] | ||
function lintJob({ cliEngineOptions, contents, eslint, filePath }) { | ||
const cliEngine = new eslint.CLIEngine(cliEngineOptions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, are we going to pay a performance penalty here by creating a new cliEngine
each time we perform a linting job? If there's much overhead here, maybe it makes sense to memoize it, and only create a new cliEngine
when the options change. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems like it could be a good idea. It looks like CLIEngine
constructor checks the --rulesdir
directory for updates only once when it's constructed, so this would mean that any changes to the rules in the rules directory wouldn't be reflected immediately. (I suspect it's pretty rare for users to modify the rules directory in the common case.)
Also, it's worth noting that the cli
API that we previously used constructs a new instance of CLIEngine
on every invocation too, so I doubt this will hurt performance compared to the previous behavior (although there might be room for improvement).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, let's leave this how it is and possibly try to memoize it in another PR.
it('will not give warnings when autofixing the file', () => { | ||
waitsForPromise(() => | ||
atom.workspace.open(ignoredPath).then(editor => | ||
fix(editor).then(result => expect(result).toBe('Linter-ESLint: Fix complete.')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works for now, and it was the previous behavior, but I think a better thing to do would be to actually tell the user that nothing happened because the file was ignored. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It used to be that way, but users complained, which is why it's this way in the first place. Can't please everyone I guess.
Personally I would prefer to have the messages shown myself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean we used to show the 'file ignored' messages when linting. And yeah, that's noisy. But when a user manually runs the fix command, we should show a meaningful message. I'll push a PR later if I can get around to it.
Approved and merging, thanks again! I can't believe this ended up being such a simple change. |
Agreed. Thanks @not-an-aardvark! We should have tried this a lot sooner. 😂 |
This updates linter-eslint to use ESLint's CLIEngine API rather than the private
cli
object. This makes the logic for interacting with ESLint much simpler.I'm not very familiar with the linter-eslint codebase or Atom plugins in general, so be sure to review this thoroughly.