Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New: Allow passing a function as fix option (fixes #8039) #8730

Merged
merged 8 commits into from Jun 29, 2017

Conversation

IanVS
Copy link
Member

@IanVS IanVS commented Jun 14, 2017

What is the purpose of this pull request? (put an "X" next to item)

[ ] Documentation update
[ ] Bug fix (template)
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofixing to a rule
[ ] Add a CLI option
[X] Add something to the core
[ ] Other, please explain:

Closes #8039

What changes did you make? (Give an overview)
The main change here is to allow the fix option of the CLIEngine to be either a boolean or now also a function which is given each linting message and is expected to return a boolean. This will give users greater control over what kind of fixes are applied by ESLint.

Is there anything you'd like reviewers to focus on?
This may be somewhat incomplete. I worked on it a few months ago, and then put it aside. I've rebased on latest master and believe the happy path works correctly, but there may need to be more safety added around executing the function that is provided as the fix option, as right now all it does is check for truthiness of the return value (which is maybe fine, but is worth some consideration).

I'm happy to continue working on this to get it across the finish line, based on feedback from reviewers. Or alternatively if anyone wants to jump in and take it over, I'm happy to turn it over as well.

@eslintbot
Copy link

Thanks for the pull request, @IanVS! I took a look to make sure it's ready for merging and found some changes are needed:

  • The commit summary needs to begin with a tag (such as Fix: or Update:). Please check out our guide for how to properly format your commit summary and update it on this pull request.

Can you please update the pull request to address these?

(More information can be found in our pull request guide.)

@mention-bot
Copy link

@IanVS, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas, @gyandeeps and @roadhump to be potential reviewers.

@kaicataldo kaicataldo added accepted There is consensus among the team that this change meets the criteria for inclusion core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint labels Jun 14, 2017
@IanVS IanVS changed the title Allow passing a function as fix option (fixes #8039) New: Allow passing a function as fix option (fixes #8039) Jun 15, 2017
@eslintbot
Copy link

LGTM

@IanVS
Copy link
Member Author

IanVS commented Jun 15, 2017

I've rebased this to account for the recent addition of linter.verifyAndFix().

@not-an-aardvark not-an-aardvark self-requested a review June 19, 2017 02:49
Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

Thanks! This looks good, I just have a few minor suggestions.

lib/linter.js Outdated
* @returns {Object} The result of the fix operation as returned from the
* SourceCodeFixer.
*/
verifyAndFix(text, config, options) {
verifyAndFix(text, config, options, shouldFix) {
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: Could fix be one of the options (third parameter) rather than adding another parameter to this method (e.g. as a fixFilter property)? It would be nice to avoid the boolean trap.

const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START], shouldFixSpy);

assert.equal(result.output, TEST_CODE);
});
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a test to assert that the fix filter is called as a pure function (without a this value)? I don't want to accidentally expose internal objects because that could make it difficult to change the implementation later if people start to rely on the this value.

const result = SourceCodeFixer.applyFixes(sourceCode, [INSERT_AT_START], shouldFixSpy);

assert.equal(result.output, TEST_CODE);
});
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a test that asserts that only the fixes for which the fix filter returns true are applied? All of the filters in the existing tests appear to either always return true or always return false, but as a sanity check it would be useful to make sure the function works when it only sometimes returns true.

output = "";
if (typeof shouldFix === "function") {
if (shouldFix(problem)) {
fixesWereApplied = attemptFix(problem) || fixesWereApplied;
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 correctly, attemptFix will only return false if the current fix conflicts with another fix that has already been applied. In that case, will fixesWereApplied ever be false after this line?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, that's a great point. I can't remember my thinking here, but I think I was trying to be cautious and not build in assumptions about the conditions under which attemptFix would return true or false. But, the diff shows that currently we always return true from this block as it is. I'll be happy to simplify this down, and add a comment about why it is always true.

if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) {
output = "";
if (typeof shouldFix === "function") {
if (shouldFix(problem)) {
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: Maybe this could be simplified to

if (typeof shouldFix !== "function" || shouldFix(problem)) {
    // ...
} else {
    // ...
}

to avoid nesting/duplication.

return {
fixed: false,
messages,
output: ""
Copy link
Member

Choose a reason for hiding this comment

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

Should the output here be sourceCode.text rather than the empty string, since the resulting text is the same as the original text? (I'm not sure whether the other parts of the code check the fixed value before using the output, but setting the output to the original text seems like it would be least surprising in case fixed isn't checked.)

@IanVS
Copy link
Member Author

IanVS commented Jun 19, 2017

Thanks for the review and great suggestions, @not-an-aardvark. I'll make your requested changes within the next few days.

@eslintbot
Copy link

LGTM

const fix = problem.fix;
const start = fix.range[0];
const end = fix.range[1];
if (typeof shouldFix !== "function" || shouldFix(problem)) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Note, we are only checking "truthiness" here. I think that's okay, but the other option would be to check for an explicit true return value.

@IanVS
Copy link
Member Author

IanVS commented Jun 25, 2017

Thanks again, @not-an-aardvark. I believe I've addressed your comments.

Copy link
Member

@not-an-aardvark not-an-aardvark left a comment

Choose a reason for hiding this comment

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

LGTM, thanks!

@eslintbot
Copy link

LGTM

This way, the code that uses this doesn’t need to necessarily check
the value of `fixed`.
If we’ve gotten to this point, at least one fix will have been applied.
Meaning, that they cannot access the `this` value of SourceCodeFixer.
This is to verify that the problem can be used to return true or false
conditionally, and that eslint will only apply fixes when true is
returned.
@eslintbot
Copy link

LGTM

@not-an-aardvark not-an-aardvark merged commit c693be5 into master Jun 29, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepted There is consensus among the team that this change meets the criteria for inclusion archived due to age This issue has been archived; please open a new issue for any further discussion core Relates to ESLint's core APIs and features enhancement This change enhances an existing feature of ESLint
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow more control over fixes in CLIEngine
7 participants