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

Use ESLint's CLIEngine API rather than the command line (fixes #797) #873

Merged
merged 3 commits into from
Apr 6, 2017

Conversation

not-an-aardvark
Copy link
Contributor

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.

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.

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.

Copy link
Member

@IanVS IanVS left a 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 })
Copy link
Member

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?

Copy link
Contributor Author

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.)

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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) {
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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 = []
Copy link
Member

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)
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 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
)

Copy link
Member

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)
Copy link
Member

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?

Copy link
Contributor Author

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).

Copy link
Member

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.'))
Copy link
Member

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. :)

Copy link
Member

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.

Copy link
Member

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.

@Arcanemagus
Copy link
Member

Approved and merging, thanks again! I can't believe this ended up being such a simple change.

@Arcanemagus Arcanemagus merged commit b6bdeb1 into AtomLinter:master Apr 6, 2017
@not-an-aardvark not-an-aardvark deleted the use-node-api branch April 6, 2017 05:32
@IanVS
Copy link
Member

IanVS commented Apr 6, 2017

Agreed. Thanks @not-an-aardvark! We should have tried this a lot sooner. 😂

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants