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
Update: allow autofixing when using processors (fixes #7510) #9090
Conversation
@not-an-aardvark, thanks for your PR! By analyzing the history of the files in this pull request, we identified @gyandeeps, @mysticatea and @nzakas to be potential reviewers. |
c9cdaa9
to
6475a80
Compare
LGTM |
1 similar comment
LGTM |
That's great! I didn't look at the implementation in details yet, but I'll try to adapt my plugin to work with this branch, to give you my feedbacks if needed. |
c780ebc
to
e4a2622
Compare
LGTM |
Thoughts after writing a simple implementation for this as part of the tests:
|
e4a2622
to
01b23b8
Compare
LGTM |
01b23b8
to
c6d7567
Compare
LGTM |
c6d7567
to
fdfd775
Compare
LGTM |
TSC Summary: This issue proposes allowing autofixing to occur when using processors, by having processors modify autofix locations as necessary as part of the TSC Question: Should we accept this proposal? If so, should we make this available to processors by default, or add an opt-in for a processor to declare that it supports autofixing? |
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.
In general, I'm fine with those changes, however, I don't think they will cover all use-cases. And in some cases, they will make it impossible to restore code back into original format, I think. Like when you had to substitute some variables/names in preprocessor. You might not be able to find them again in postprocessor after fixes.
lib/cli-engine.js
Outdated
const fixedResult = linter.verifyAndFix(text, config, { | ||
filename, | ||
allowInlineConfig, | ||
fix: typeof fix !== "undefined" && fix, |
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 would result in performance impact, I would guess. Now we are trying to fix every error, even if we don't need to.
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 don't think so -- SourceCodeFixer
has a fast path to just skip all fixes when fix: false
is used.
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 mean in the case of processor not supporting autofixing (and not wanting to support it in the future). Autofixing will still happen, but it's results will be ignored by postprocess
function.
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.
Oh, I see. This could be addressed by making autofixing opt-in for processors (#9090 (comment)). Currently, fixes would still get applied by ESLint in this case, but the processor would usually generate broken code.
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.
Opt-in sounds fine to me.
lib/linter.js
Outdated
* Mostly useful for testing purposes. | ||
* @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied. | ||
* Useful if you want to validate JS without comments overriding rules. | ||
* @param {function(string): string[]} [filenameOrOptions.preprocess] preprocessor for source text. If provided, this should |
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.
Since this is a public API, this would make it a breaking change.
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.
Could you elaborate on why it would be a breaking change? It seems like this is just adding additional functionality, not breaking any existing functionality.
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.
Ah, sorry, for some reason I though it's a new argument, not a new property on the object. I'm not really sure how to treat those. On one hand it's a change of a signature of publicly available API, on the other - JS works without it just fine, so it's not a breaking change, I guess.
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 don't think there's any ambiguity here. Code that would previously work when using the API will still work, so it's not a breaking change. (This would also be the case if a new argument was added.)
lib/linter.js
Outdated
const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]); | ||
const postprocess = filenameOrOptions && filenameOrOptions.postprocess || lodash.flatten; | ||
|
||
return postprocess( |
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 liked it a lot more when processors were limited to cli-engine, honestly, but I think that's a personal preference.
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 share @ilyavolodin's preference here - organizationally, it feels like Linter
shouldn't know about processors. I don't have a specific rule of thumb or design pattern or anything like that to support that feeling, unfortunately - maybe separation of concerns, but one could argue that's too broad.
However, #9090 (comment) clarified for me that postprocess
needs to be run in between getting errors in verify
and applying fixes. I don't see an easy way around that, so thankfully preprocess
and postprocess
can be treated as black boxes from Linter
's perspective.
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 this is on the right track. Other than making autofixing opt-in, I don't see any hangups. I'll give this another pass once that change is made. Nice work @not-an-aardvark!
@@ -80,7 +80,7 @@ processors: { | |||
The `preprocess` method takes the file contents and filename as arguments, and returns an array of strings to lint. The strings will be linted separately but still be registered to the filename. It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items. | |||
|
|||
The `postprocess` method takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input | |||
array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the location of all errors and aggregate them into a single flat array and return it. | |||
array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the location of all errors, (including the range in the `fix` property, if present), and aggregate them into a single flat array and return it. |
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.
Noting that the text about adjusting the fix
property, inserted after "must adjust the location of all errors" will need to change once processor autofixing is made opt-in - perhaps by moving it into its own sentence.
|
||
1. This option throws an error when code is piped to ESLint. | ||
1. This option has no effect on code that uses processors. | ||
This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. Not all problems are fixable using this option, and this option throws an error when code is piped to ESLint. |
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.
Noting that this will also need to change slightly once autofixing in processors becomes opt-in to mention that a processor might not support it.
lib/linter.js
Outdated
@@ -1181,6 +1210,11 @@ class Linter { | |||
* @param {boolean} options.allowInlineConfig Flag indicating if inline comments | |||
* should be allowed. | |||
* @param {boolean|Function} options.fix Determines whether fixes should be applied | |||
* @param {Function} options.preprocess preprocessor for source text. If provided, this should | |||
* this should accept a string of source text, and return an array of code blocks to lint. |
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 should" is duplicated at the end of the previous line and the beginning of this line.
lib/linter.js
Outdated
const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]); | ||
const postprocess = filenameOrOptions && filenameOrOptions.postprocess || lodash.flatten; | ||
|
||
return postprocess( |
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 share @ilyavolodin's preference here - organizationally, it feels like Linter
shouldn't know about processors. I don't have a specific rule of thumb or design pattern or anything like that to support that feeling, unfortunately - maybe separation of concerns, but one could argue that's too broad.
However, #9090 (comment) clarified for me that postprocess
needs to be run in between getting errors in verify
and applying fixes. I don't see an easy way around that, so thankfully preprocess
and postprocess
can be treated as black boxes from Linter
's perspective.
Thanks for the reviews! Bikeshed: It looks like there's consensus that processors should have to opt-in to autofixing. What should the API for that look like? Right now, I'm thinking that processors could have a boolean |
A boolean in the exported properties sounds good to me. One other question I have based on your list of things happening in this PR. How does this affect multi-pass autofixing? If we don't modify the code directly, we can't do multi-pass, however, if we do modify the code, postprocessor might not be able to figure out how to change it back to the original format (as I outlined in my comment). |
I described how multi-pass autofixing works in the original PR and this comment. Basically, |
LGTM |
f7a215d
to
6ddc6c7
Compare
LGTM |
@not-an-aardvark Sorry, I forgot that original comments contained the explanation. That answers my question, and I think it should work just fine. |
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.
Nice work on this, @not-an-aardvark. Makes sense 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.
Nice work @not-an-aardvark!
lib/linter.js
Outdated
* Mostly useful for testing purposes. | ||
* @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied. | ||
* Useful if you want to validate JS without comments overriding rules. | ||
* @param {function(string): string[]} [filenameOrOptions.preprocess] preprocessor for source text. If provided, this should |
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.
Typo: this should
is repeated in this comment
LGTM |
24d10db
to
0acd744
Compare
LGTM |
This was accepted in today's TSC meeting. |
What is the purpose of this pull request? (put an "X" next to item)
[x] Add something to the core
What changes did you make? (Give an overview)
This implements the proposal from #7510 (comment) to allow autofixes to be applied when using processors.
The processor API basically remains the same. The only changes are:
supportsAutofix: true
property, which opts a processor into autofixing.postprocess
method is expected to also transform autofix ranges as necessary when it transforms the locations of reported problems. Afterwards, the transformed fixes are applied to the original, unprocessed text.Multipass autofixing works by calling
preprocess
andpostprocess
for each pass.(One alternative approach would be to run multipass autofixing on the processed text, and only return the fixed text to the postprocessor afterwards. As discussed in #7510, though, that would make it difficult for postprocessors because they would need to figure out how to split the fixed text into code blocks again, which would require some sort of imprecise diffing against the original text.)
Summary of changes:
linter.verify
andlinter.verifyAndFix
now accept optionalpreprocess
andpostprocess
arguments for splitting up the text into code blocks and combining the messages from each code block. By default, the text is "preprocessed" into a single code block containing the code, and the problems are "postprocessed" by flattening the array. (This is the same as not applying a processor at all.)Linter
rather thanCLIEngine
. (However, determining which processors need to be applied for a given file is still the responsibility ofCLIEngine
.)TODO:
Is there anything you'd like reviewers to focus on?
Is this a breaking change? (It seems like if a processor currently doesn't apply a transformation to fix ranges, then the original fix ranges will get used, which might be wildly incorrect and result in broken code.) One way to avoid this could be to have processors opt-in to allowing autofixes.postprocess
withoutpreprocess
? The defaultpostprocess
(flattening the array of problems) will almost always do the wrong thing if multiple code blocks are being linted, since some of the locations will probably be invalid. It might be better to just throw a validation error.